vue3 源码简版体验

1,000 阅读4分钟

vue2响应式弊端:

响应化过程需要递归遍历,消耗较大

新加或删除属性无法监听

数组响应化需要额外实现

Map、Set、Class等无法响应式

修改语法有限制

Vue3响应式原理剖析 step1


 function reactive(obj) {
   if (typeof obj !== null && typeof obj != 'object') {
     return obj
   }

   //代理
   const observed = new Proxy(obj, {
    //  target 代理目标
     get(target, key, receiver) {
      //  Reflect 是把Object 上面的方法移到这里
      // 比Object更健壮 避免一些异常
      const res = Reflect.get(target,key, receiver)
      console.log('获取'+ key +":"+ res)
      return res
     },
     set(target, key, value,receiver) {
      const res = Reflect.get(target,key,value, receiver)
      console.log('设置'+ key+ ':'+ value)
      return res
     },
     deleteProperty(target, key) {
       const res = Reflect.deleteProperty(target, key)
       console.log('删除'+ key)
       return res
     } 
   })
   return observed
 }


 const state = reactive({
   foo: 'foo',
   bar: {a: 1},
   arr: [0, 1,2,3]
 })

 state.foo
 state.foo = 'f000000'

 state.abai = 'a Bai'

 delete state.abai
//  
 state.arr[0] = 10

step2

state.bar.a = 10  // 获取bar: [object Object]

**嵌套对象响应式 测试:嵌套对象不能响应 **

Q: 访问bar.a属性的时候没有代理了

A: 对代理的方法get 重新做一层代理

 get(target, key, receiver) {
  //  Reflect 是把Object 上面的方法移到这里
  // 比Object更健壮 避免一些异常
  const res = Reflect.get(target,key, receiver)
  console.log('获取'+ key +":"+ res)
  // 返回的结果是对象 重新做一层代理
  // 好处是 访问的时候才递归
  return isObject(res) ? reactive(res) : res
 },

step3

Q:重复代理问题? A:把代理过的对象缓存起来

const toProxy = new WeakMap() // {obj: observed}
const toRaw = new WeakMap() // {observed: obj}
...
function reactive(obj) {
//  查找缓存
  if (toProxy.has(obj)) {
    console.log('toProxy', toProxy.get(obj))
    return toProxy.get(obj)
  }
  // 用户传的代理过的对象 直接返回
  if (toRaw.has(obj)) {
    return obj
  }
  const observed = new Proxy(obj, {...
  })
  // 缓存
  toProxy.set(obj, observed)

  toRaw.set(observed, obj)
  return observed
}  

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

step4

依赖收集:

建立响应数据key和更新函数之间的对应关系

// 设置响应函数 
effect(() => console.log(state.foo)) 
// 用户修改关联数据会触发响应函数
state.foo = 'xxx'

设计

实现三个函数: effect:将回调函数保存起来备用,立即执行一次回调函数触发它里面一些响应数据的getter

track:getter中调用track,把前面存储的回调函数和当前

target,key之间建立映射关系

trigger:setter中调用trigger,把target,key对应的响应函数都执行一遍

target,key和响应函数映射关系

// 大概结构如下所示 
// target | depsMap
 // obj | key | Dep 
        // k1 | effect1,effect2... 
        // k2 | effect3,effect4... 
// {target: {key: [effect1,...]}}

实现的代码

在observed中的get函数中调用

//收集依赖 track(target, key) 在set()中取出 trigger(target, key)

// 保存响应函数fn
const effectStack = []
function effect(fn) {
  // 执行之前入栈, 执行完 出栈
  const Effect = function () {
    try {
      effectStack.push(fn)
      // 执行函数 触发getter ->track
      fn()
    } catch(error) {

    } finally {
      // 出战
      effectStack.pop()
    }
  }
  Effect()
  return Effect
}
// 建立tager/key/cb之间的映射关系
const targetMap = new WeakMap()
function track(target,key) {
  // 取出响应函数
  const effect = effectStack[effectStack.length -1]
  if (effect) {
    // 获取依赖表
    let depsMap = targetMap.get(target) // 
    if (!depsMap) {
      // 初始化时不存在,需要传教一个新的空的Map
      depsMap = new Map()
      targetMap.set(target, depsMap) // 对应数据结构: target | depsMap
    }
    // 获取key 对应的函数集合
    let deps = depsMap.get(key)
    if (!deps) {
      deps = new Set()
      depsMap.set(key, deps)
    }
   // 把当前effect 加入deps中
    if (!deps.has(effect)) {
      deps.add(effect)
    }
  }
}

function trigger(target, key) {
  // 获取依赖 set
  const depsMap = targetMap.get(target) // 获取第一层结构
  if (depsMap) {
    const deps = depsMap.get(key) // key层 所有的回调函数
    // deps 存在 遍历 执行
    deps && deps.forEach(effect => effect())
  }
}

完整demo

const { displayPartsToString } = require("typescript")

const isObject = val => val !== null && typeof val === 'object'

const toProxy = new WeakMap() // {obj: observed}
const toRaw = new WeakMap() // {observed: obj}


 function reactive(obj) {
   if (!isObject(obj)) {
     return obj
   }
  //  查找缓存
  if (toProxy.has(obj)) {
    console.log('toProxy', toProxy.get(obj))
    return toProxy.get(obj)
  }
  // 用户传的代理过的对象 直接返回
  if (toRaw.has(obj)) {
    return obj
  }
   //代理
   const observed = new Proxy(obj, {
    //  target 代理目标
     get(target, key, receiver) {
      //  Reflect 是把Object 上面的方法移到这里
      // 比Object更健壮 避免一些异常
      const res = Reflect.get(target,key, receiver)
      console.log('获取'+ key +":"+ res)
      //收集依赖
      track(target, key) 

      // 返回的结果是对象 重新做一层代理
      // 好处是 访问的时候才递归
      return isObject(res) ? reactive(res) : res
     },
     set(target, key, value,receiver) {
      const res = Reflect.get(target,key,value, receiver)
      trigger(target, key)
      console.log('设置'+ key+ ':'+ value)
      return res
     },
     deleteProperty(target, key) {
       const res = Reflect.deleteProperty(target, key)
       console.log('删除'+ key)
       return res
     } 
   })
  // 缓存
  toProxy.set(obj, observed)

  toRaw.set(observed, obj)
  
  return observed
}
// 保存响应函数fn
const effectStack = []
function effect(fn) {
  // 执行之前入栈, 执行完 出栈
  const Effect = function () {
    try {
      effectStack.push(fn)
      // 执行函数 触发getter ->track
      fn()
    } catch(error) {

    } finally {
      // 出战
      effectStack.pop()
    }
  }
  Effect()
  return Effect
}
// 建立tager/key/cb之间的映射关系
const targetMap = new WeakMap()
function track(target,key) {
  // 取出响应函数
  const effect = effectStack[effectStack.length -1]
  if (effect) {
    // 获取依赖表
    let depsMap = targetMap.get(target) // 
    if (!depsMap) {
      // 初始化时不存在,需要传教一个新的空的Map
      depsMap = new Map()
      targetMap.set(target, depsMap) // 对应数据结构: target | depsMap
    }
    // 获取key 对应的函数集合
    let deps = depsMap.get(key)
    if (!deps) {
      deps = new Set()
      depsMap.set(key, deps)
    }
   // 把当前effect 加入deps中
    if (!deps.has(effect)) {
      deps.add(effect)
    }
  }
}

function trigger(target, key) {
  // 获取依赖 set
  const depsMap = targetMap.get(target) // 获取第一层结构
  if (depsMap) {
    const deps = depsMap.get(key) // key层 所有的回调函数
    // deps 存在 遍历 执行
    deps && deps.forEach(effect => effect())
  }
}

const state = reactive({
  foo: 'foo',
  bar: {a: 1},
  arr: [0, 1,2,3]
})

effect(() => {
  console.log('effect1:'+ state.foo)
})
// effect(() => {
//   console.log('effect2'+ state.foo)
// })

//  state.foo
 state.foo = 'f000000'

//  state.abai = 'a Bai'

//  delete state.abai
// //  
//  state.arr[0] = 10
//  state.bar.a = 10 // 获取bar: [object Object]
 
// console.log(reactive(state) === state)