乐趣区

vue30数据响应式原理

配置环境

  1. git clone https://github.com/vuejs/vue-next.git
  2. 安装依赖: yarn
  3. 生成 sourcemap 文件,修改 package.json
  4. "dev": "node scripts/dev.js --sourcemap"
  5. 编译: yarn dev

sourcemap
dev 命令执行的文件是 node scripts/dev.js,打开后可以看到
在后面传递的选项是在 minimist 中生效的。

解析为一个对象,args

源码结构

  • 所有内容以模块化的方式进行展示,源码在 packages 里面;

  • 检验是否打包成功:vue-next/packages/vue/dist/可能这个路径是否存在,vue.global.js.map文件

测试的 demo 文件,路径:vue-next/packages/vue/examples/classic文件中

比如打开一个 todolist,或者自己创建测试文件,也是在这个文件夹下 (vue-next/packages/vue/examples):vue-next/packages/vue/examples/classic/todomvc.html

可以打断点,来看 createApp 如何创建

vue3.0 的使用

  1. option 的 API 还是可以继续使用的,比如“@click”
  2. 新增 Composition API,基于函数的逻辑服用组合, 相关的逻辑放在一个 setup 中

Composition API 的使用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <p  @click="onclick">
      {{state.message}}
    </p>
  </div>
  <script src="../dist/vue.global.js"></script>
  <script>
    const {createApp, reactive, onMounted} = Vue
    const app = createApp({setup(){
        // 声明变量
        const state=reactive({message:'vue-3.0'})
        // 事件
        function onclick(){
          state.message="点击 vue3.0"
          console.log('点击.....')
        }
        // 生命周期 
        onMounted(()=>{console.log('挂载完成,可以获取 dome 元素')
        })// 接收一个函数
        // 导出
        return {state, onclick}
      }
    })
    app.mount('#app')
  </script>
</body>
</html>
单值响应式
  // 单值响应式
  const counter =ref(0)

  // 修改单值响应式,因为用了 ref 是一个对象,修改需要访问它的 value
  setInterval(()=>{counter.value++},
转化单值响应式

toRefs:可以把一个响应式的对象,里面的每个属性转化成单值的 ref 对象;在进行展开(…)之后,就都是单值的响应式。
在页面中,想直接 使用 message,而不是 {{state.message}};
不能在导出的时候,结构 state;…state 是错误的行为;会破坏数据响应式。再去触发点击事件的时候,会发现无效;无法实现数据响应式。

...toRefs(state)
// toRefs 会将每个属性转化成单值的 ref 对象
// 在进行展开(...)之后,就都是单值的响应式。

vue3.0 数据响应式原理

vue2.0 的数据响应式
  1. 对象响应化:遍历 key,设置 getter,setter
  2. 数据响应化:覆盖数组方法
vue2.0 数据响应式弊端
  1. 响应化过程需要递归遍历,消耗较大
  2. 新加或删除属性无法监听
  3. 数组响应化需要额外实现
  4. Map、Set、Class 等无法响应式
  5. 修改语法有限制
vue3.0 数据响应式采用 ES6 的 proxy 特性,对象和数组都可以拦截
const observer = new Proxy(obj,{get(){},
    set(){},
    deleteProperty(){}
})
数据拦截

function reactive(obj){
  // 将传进来的对象,做一次代理
  if(typeof obj!=='object'){ // 不是对象就直接 return 
    return obj
  }
  // 做代理
  // https://es6.ruanyifeng.com/#docs/proxy
  const observed = new Proxy(obj,
    {
      // 获取
      get(target, key){console.log('get',key)
        // http://es6.ruanyifeng.com/#docs/reflect
        return Reflect.get(target,key)
      },
      // 修改
      set(target, key,value){console.log('set', key)
        return Reflect.set(target,key, value)
      },
      // 删除
      deleteProperty(target, key){console.log('del',key)
        return Reflect.deleteProperty(target, key)
      }
    })
  return observed
}

const state =reactive({name:'ohhh'})

state.name
state.name ='ohhhhhhh'
delete state.name

  • 拦截两层的话,还需要修改:
 const state =reactive({
    a:{b:3333}
 })
const isObject = val =>val !==null && typeof val==='object'
  // 获取
  get(target, key){console.log('get',key)
    // http://es6.ruanyifeng.com/#docs/reflect
    res = Reflect.get(target,key)
    console.log('res',res)
    return isObject(res)?reactive(res):res
  },

  • 对象里存对象 - 补全代码
 const isObject = val =>val !==null && typeof val==='object'
function reactive(obj){
  // 将传进来的对象,做一次代理
  if(!isObject(obj)){ // 不是对象就直接 return 
    return obj
  }
  // 做代理
  // https://es6.ruanyifeng.com/#docs/proxy
  const observed = new Proxy(obj,
    {
      // 获取
      get(target, key){console.log('get',key)
        // http://es6.ruanyifeng.com/#docs/reflect
        res = Reflect.get(target,key)
        console.log('res',res)
        return isObject(res)?reactive(res):res
      },
      // 修改
      set(target, key,value){console.log('set', key)
        return Reflect.set(target,key, value)
      },
      // 删除
      deleteProperty(target, key){console.log('del',key)
        return Reflect.deleteProperty(target, key)
      }
    })
  return observed
}

const state =reactive({
  name:'ohhh',
  a:{b:333}
})

// state.name
// state.name ='ohhhhhhh'
// delete state.name
state.a.b

  • 避免重复代理
reactive(state)
// 缓存
// key 可以为对象,可以存储为:{obj(穿进来的纯对象,还没有被代理过):observerd(代理过的对象)}
const toProxy = new WeakMap()
const toRaw = new WeakMap() // 这个就是相反的{observed:obj}
  // 检测缓存
  if(toProxy.has(obj)){
    // 代理过了,直接返回缓存结果
    return toProxy.get(obj)
  }
  if(toRaw.has(obj)){
    // key 已经是代理对象,直接返回
    return obj
  }
  // 做缓存
  toProxy.set(obj,observed)
  toRaw.set(observed,obj)
console.log(reactive(state)===state) // true

完整代码:

const isObject = val =>val !==null && typeof val==='object'
// 缓存
// key 可以为对象,可以存储为:{obj(穿进来的纯对象,还没有被代理过):observerd(代理过的对象)}
 const toProxy = new WeakMap()
 const toRaw = new WeakMap() // 这个就是相反的{observed:obj}

function reactive(obj){
  // 将传进来的对象,做一次代理
  if(!isObject(obj)){ // 不是对象就直接 return 
    return obj
  }
  // 检测缓存
  if(toProxy.has(obj)){
    // 代理过了,直接返回缓存结果
    return toProxy.get(obj)
  }
  if(toRaw.has(obj)){
    // key 已经是代理对象,直接返回
    return obj
  }

  // 做代理
  // https://es6.ruanyifeng.com/#docs/proxy
  const observed = new Proxy(obj,
    {
      // 获取
      get(target, key){console.log('get',key)
        // http://es6.ruanyifeng.com/#docs/reflect
        res = Reflect.get(target,key)
        console.log('res',res)
        return isObject(res)?reactive(res):res
      },
      // 修改
      set(target, key,value){console.log('set', key)
        return Reflect.set(target,key, value)
      },
      // 删除
      deleteProperty(target, key){console.log('del',key)
        return Reflect.deleteProperty(target, key)
      }
    })
  // 做缓存
  toProxy.set(obj,observed)
  toRaw.set(observed,obj)
  return observed
}

const state =reactive({
  name:'ohhh',
  a:{b:333}
})

// state.name
// state.name ='ohhhhhhh'
// delete state.name
// state.a.b

console.log(reactive(state)===state)

依赖收集
const isObject = val =>val !==null && typeof val==='object'
// 缓存
// key 可以为对象,可以存储为:{obj(穿进来的纯对象,还没有被代理过):observerd(代理过的对象)}
 const toProxy = new WeakMap()
 const toRaw = new WeakMap() // 这个就是相反的{observed:obj}

function reactive(obj){
  // 将传进来的对象,做一次代理
  if(!isObject(obj)){ // 不是对象就直接 return 
    return obj
  }
  // 检测缓存
  if(toProxy.has(obj)){
    // 代理过了,直接返回缓存结果
    return toProxy.get(obj)
  }
  if(toRaw.has(obj)){
    // key 已经是代理对象,直接返回
    return obj
  }

  // 做代理
  // https://es6.ruanyifeng.com/#docs/proxy
  const observed = new Proxy(obj,
    {
      // 获取
      get(target, key){console.log('get',key)
        // http://es6.ruanyifeng.com/#docs/reflect
        const res = Reflect.get(target,key)
        console.log('res',res)
        track(target, key)
        return isObject(res)?reactive(res):res
      },
      // 修改
      set(target, key,value){console.log('set', key)
        const ret = Reflect.set(target,key, value)
        trigger(target, key)
        return ret
      },
      // 删除
      deleteProperty(target, key){console.log('del',key)
        trigger(target, key)
        return Reflect.deleteProperty(target, key)
      }
    })
  // 做缓存
  toProxy.set(obj,observed)
  toRaw.set(observed,obj)
  return observed
}
// 临时保存响应函数
const effectStack =[]

function effect(fn){const rxEffect =function(){
    try {
      // 入栈
      effectStack.push(rxEffect)
      return fn()} finally {
      // 出栈
      effectStack.pop()}
  }
  // 立即调用
  rxEffect()
  return rxEffect
}
// 存储数据关系的结构 , 大管家
// 数据结构 {target:{key: [cb1,cb2, ......]}}
const targetMap= new WeakMap()

// 收集依赖
function track(target, key){//  console.log(target, key,'000000000')
  // 获取响应关系
  const effect = effectStack[effectStack.length-1]
  if(effect){
    // 获取 target 的依赖关系
    let depsMap = targetMap.get(target)

    if(!depsMap){
      // 首次不存在 
      depsMap = new Map()
      // 创建
      targetMap.set(target, depsMap)
    }
    // 获取 key 对应的响应函数
    let deps = depsMap.get(key)
    if(!deps){deps =new Set()
      depsMap.set(key,deps)
    }
    // 存放一次回调函数
    if(!deps.has(effect)){deps.add(effect)
    }
  }
}
// 触发执行
function trigger(target, key){const depsMap = targetMap.get(target)
  if(depsMap){
    // 获取响应的集合
    const deps =depsMap.get(key)
    if(deps){deps.forEach(effect=>effect())
    }
  }
}

const state =reactive({
  name:'ohhh',
  a:{b:333}
})
// 激活 get
// 最开始的时候会执行一次
effect(()=>{console.log('effect1' , state.name)
})
effect(()=>{console.log('effect2' , state.name)
})
state.name ='ohhhhhhh'


先写到这里,目前还在看源码这块,我先把响应式原理这块分析写出来,这样也有助于梳理学习思路~~,后面会陆陆续续更新哈。

退出移动版