Vue3.X响应式原理

305 阅读2分钟

Vue3.X响应式对比Vue2.X优点

Vue2.X中使用Object.difineProperty做数据劫持,并且不支持数组,需要覆盖数组的7个方法,额外增加通知逻辑,对新增的属性拦截不到,需要额外的api来实现,并且在初始化时需要大量的observer,dep,watcher去做数据响应式处理,互相添加映射关系,消耗大量内存。

Vue3.X中使用Proxy代理搭配Reflect反射做响应式处理,初始化并不会做处理,只有在使用时才去做代理。

实现原理

58488a5332571ad1adc08ece4680d53.png

相关api有:

  • effect(fn):传⼊fn,返回的函数将是响应式的,内部代理的数据发⽣变化,它会再次执⾏
  • track(target, key):建⽴响应式函数与其访问的⽬标(target)和键(key)之间的映射关系
  • trigger(target, key):根据track()建⽴的映射关系,找到对应响应式函数并执⾏它
const isObj = v => typeof v === 'object' && v !== null

function reactive(obj) {
  if (!isObj(obj)) {
    return obj
  }
  return new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      console.log('get', key)
      //依赖收集
      track(target, key)
      return isObj(res) ? reactive(res) : res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      console.log('set', key)
      //触发副作用
      trigger(target, key)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      console.log('deleteProperty')
      //触发副作用
      trigger(target, key)
      return res
    }
  })
}
// 临时存储副作用函数
const effectStack = []

// 副作用函数:建立传入fn和其内部的依赖之间的映射关系
function effect(fn) {
  // 执行fn触发依赖的get方法
  const e = createReactiveEffect(fn)
  //   立即执行
  e()
  return e
}

function createReactiveEffect(fn) {
  //封装fn:错误处理,保存到stack
  const effect = function(...args) {
    try {
      // 入栈
      effectStack.push(effect)
      // 立即执行
      return fn(...args)
    } finally {
      // 出栈
      effectStack.pop()
    }
  }
  return effect
}

// 依赖收集
const targetMap = new WeakMap()
function track(target, key) {
  // 获取副作用函数
  const effect = effectStack[effectStack.length - 1]
  if (effect) {
    // 初始化时target这个key不存在
    let depMap = targetMap.get(target)
    if (!depMap) {
      depMap = new Map()
      targetMap.set(target, depMap)
    }
    // 从deoMap中获取副作用函数集合
    let deps = depMap.get(key)
    // 初始化时deps不存在
    if (!deps) {
      deps = new Set()
      depMap.set(key, deps)
    }
    // 放入新传入的副作用函数,使用set自带去重
    deps.add(effect)
  }
}

// 触发副作用
function trigger(target, key) {
  // 获取target,key对应的set
  const depMap = targetMap.get(target)
  if (!depMap) return
  const deps = depMap.get(key)
  if (deps) {
    // 循环执行内部所有副作用函数
    deps.forEach(dep => dep())
  }
}

const state = reactive({ foo: 'foo', bar: { n: 1 }, arr: [1, 2, 3] })
effect(() => {
  console.log('effect', state.foo)
})
effect(() => {
    console.log('effect2', state.foo,state.bar.n)
  })
state.foo='foooooo'