vue3数据响应式与依赖收集

vue3 数据响应式整体思路

  1. 使用new Proxy对响应式数据进行代理
  2. get函数
    1. 触发trace添加依赖
    2. 如果get的数据是对象,执行递归new Proxy代理(此处是懒调用,只有对象被使用才会执行new Proxy代理)
  3. set函数
    1. 触发trigger执行get函数收集的相关依赖

vue3响应式和vue2的变化

  • 从Object.definePrototype =》 new Proxy

    • 优点
    1. 对整个对象使用Proxy代理,并且对象是懒调用(未被使用的对象,不会添加代理),免去整个对象逐个key遍历的性能消耗
    2. 数组不需要特殊处理(vue2中重写 push pop shift unshift reverse sort splice 七个方法)
    3. 能给监听ES6的新对象 如 Map Set之类
    • 缺点
    1. 不兼容ie,如果想使用composition api可以等vue2.7版本的更新
  • 依赖收集

    • vue2
    • vue3
      • effect的概念,代表某个动作引起的额外效果/影响。(当effect执行后,会建立起数据和fn的依赖关系,数据变更后触发fn执行)
      • 依赖关系建立过程
        1. effect(fn)(在mount挂载的时候,对把update作为参数fn执行effect函数)
        2. fn入栈(effectStack)
        3. 执行一次fn
        4. fn函数中使用响应式数据,触发依赖(get)
        5. 触发trace函数,存储依赖
        6. targetMap.get(target)=>depMap.get(key)=>deps。 fn存入deps中
        7. effectStack出栈,结束依赖关系建立
      • 数据变更触发更新
        1. 响应式数据更新,触发set函数
        2. 触发trigger函数
        3. targetMap.get(target)=>depMap.get(key)=>deps。遍历deps并且执行(deps内存的为所有的依赖)

依赖收集原理图

依赖收集

vue3响应式简易代码

const isObject = v => typeof v === 'object'
function reactive(obj) {
  if (!isObject(obj)) {
    return obj
  }
  return new Proxy(obj, {
    // target代理后的对象, 当递归之后target可能是子对象
    get(target, key) {
      console.log('get key', key)
      // const res = target[key]
      // 触发依赖收集
      const res = Reflect.get(target, key) // 放着程序崩溃,优雅解决
      // vue2 这里是往watcher添加依赖
      // vue3 根据target key 存储再一个全局的依赖关系内
      // targetMap => depMap => deps
      trace(target, key)
      // reactive递归,懒调用,被使用到,才会加入响应式
      return isObject(res) ? reactive(res) : res
    },
    set(target, key, val) {
      console.log('set key', key)
      target[key] = val
      const res = Reflect.set(target, key, val) // 返回设置是否成功
      trigger(target, key)
      return res
    },
    deleteProperty(target, key) {
      console.log('dele ', key)
      // delete target[key]
      const res = Reflect.deleteProperty(target, key)
      return res
    }
  })
}
const state = reactive({
  foo: 'fooo',
  count: 0,
  bar: {
    baz: 1
  }
})
// 临时存储副作用函数
const effectStack = []

// 建立副作用  依赖关系
function effect(fn) {
  const e = createReactiveEffect(fn)
  // e()
  return e
}
function createReactiveEffect(fn) {
  try {
    // 类似于Dep.target 全局变量
    effectStack.push(fn)
    return fn()
  } finally {
    effectStack.pop(fn)
  }
}
// 保存依赖关系数据结构
const targetMap = new WeakMap()
// 依赖收集:建立target key fn银蛇关系
function trace(target, key) {
  // 1. 获取effect
  const effect = effectStack[effectStack.length - 1]
  if (effect) {
    // 2. 获取target对应的map
    let depMap = targetMap.get(target)
    if (!depMap) {
      depMap = new Map()
      targetMap.set(target, depMap)
    }

    // 3. 获取对应set
    let deps = depMap.get(key)
    if (!deps) {
      deps = new Set()
      depMap.set(key, deps)
    }

    // 4. effect存入deps
    deps.add(effect)
  }
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map())
  }
}
// 触发副作用:根据targe key 获取相关fns ,执行
function trigger(target, key) {
  const depMap = targetMap.get(target)
  if (depMap) {
    const deps = depMap.get(key)
    if (deps) {
      deps.forEach(dep => dep())
    }
  }
}

/**
 * 建立依赖关系
 * 1. 执行effect(fn)
 * 2. fn入栈(effectStack)
 * 2. 首次先执行fn
 * 3. 触发依赖 (state.foo)
 * 4. 触发trace
 * 5. targetMap.get(target)=>depMap.get(key)=>deps, deps存入fn
 * 6. fn出栈(effectStack)
 */
effect(() => {
  // 首次会先执行一次, 触发state.foo的get(此时effectStack内存在当前函数)
  console.log('effect1', state.foo)
  // 当effect嵌套执行的时候,effectStack会存在多个值,但是每次都从栈顶(数组最后一个 )拿数据
  //effect(() => {
  //  console.log('effect3', state.foo)
  //})
  // 嵌套的话打印日志情况
  //get key foo
  //effect1 fooo
  //get key foo
  //effect3 fooo
  //get key foo
  //effect2 fooo
  //get key foo
  //set key foo 
  //get key foo
  //effect1 foooooooo
  //get key foo
  //effect3 foooooooo
  //get key foo
  //effect3 foooooooo
  //get key foo
  //effect2 foooooooo
  //get key  foo
  //effect3 foooooooo
})
effect(() => {
  console.log('effect2', state.foo)
})
state.foo
state.foo = 'foooooooo'

// 日志打印情况
// get key foo 执行effect1内的函数,执行state.foo触发get函数
// effect1 fooo 打印effect1的日志
// get key foo 执行effect2内的函数,执行state.foo触发get函数
// effect2 fooo 打印effect2的日志
// get key foo 执行state.foo,触发get函数
// set key foo 执行state.foo = 'foooooooo',触发set函数,紧接着触发trigger函数执行所有依赖的fn
// get key foo effect1内的函数被执行,执行state.foo,触发get函数
// effect1 foooooooo 
// get key foo effect2内的函数被执行,执行state.foo,触发get函数
// effect2 foooooooo

从上面代码和测试用例来看,就完成了effect和数据的依赖,数据的响应可以触发effect函数的执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值