vue3响应式数据原理

Effect 原理解析 与 实现

引言:

vue、react 框架的核心都是数据驱动视图也就是model => view,实现的核心也就是 数据响应。

主要就三步:

  1. 创建响应式的数据 defineProperty、pxoxy。这样使用、修改数据的事件我们都能捕捉到

  2. 在使用响应式的数据时收集依赖,把跟该数据相关的副作用都储存起来

  3. 在修改响应式的数据时触发依赖,执行相关的副作用


一、effect:副作用函数

1.类似于vue2.0中watch 的升级版,如果函数中用到的响应式的数据发生了变化,则会执行该函数

// Effect 的简单应用
const component = defineComponent({
            name: 'zhenAPP',
            template: `
                
`, setup(props) { const data = reactive({ count: 0, }); console.log('-----------创建reactive------------') console.log('创建reactive对象:', data) const addHandler = () => { data.count++; }; effect(() => { console.log('1',data.count) }); return { addHandler, }; }, }); // 在 Vue.js 3.0 中,初始化一个应用的方式如下 import { createApp } from 'vue' import App from './app' const app = createApp(App) app.mount('#app') // 设置并运行带副作用的渲染函数 setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense ...)

二、proxy 与reflect

Object.defineProperty API 的一些缺点:

1. 不能监听对象属性新增和删除;
2. 初始化阶段递归执行 Object.defineProperty 带来的性能负担。

const p = new Proxy(target, handlerObject)

handlerObject = {
  get(target,key,receiver){ // receiver 可以理解成改变get函数中的this指向,默认就是handlerObject
    
    },
  set(target,key,value,receiver){
    
  }
}

vue3源码的调试方法:

  • clone vue-next
  • npm run dev
  • 新建html文件,引用打包的js
    
    
    
        vue-demo
    
    
        

三、响应式api reactive的实现

// 0 reactactive模块的存储变量:reactiveMap
//  作用1:维护整个应用数据代理,防止对同一个对象重复代理,比如父子组件共享某个响应数据
//  作用2:weakmap 本身的优势,这样某个组件卸载之后,组件上的响应数据也会被删除,reactiveMap也会删除响应数据。防止内存泄露
export const reactiveMap = new WeakMap()

// 1.reactive 方法
export function reactive(target: object) {
  // ...排除不能代理的一些情况
  return createReactiveObject(
    target,
    mutableHandlers,
  )
}

// 2.createReactiveObject proxy代理
function createReactiveObject(
  target: Target,
  baseHandlers: ProxyHandler,
) {
 
  // 代理去重 target already has corresponding Proxy
  const proxyMap =  reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 利用proxy生成响应式对象 
  const proxy = new Proxy(
    target,
    mutableHandlers
  )
  // 存入map
  proxyMap.set(target, proxy)
  return proxy
}

// 3.baseHandlers
const mutableHandlers = {
  get: createGetter(),
  set: createSetter(),
}
// 4. get 函数的代理:调用了track 方法
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {

    const res = Reflect.get(target, key, receiver)
        //重点: track 实现依赖收集。effect 中接下来会分析
    track(target, TrackOpTypes.GET, key)
    
    if (isObject(res)) {
      // 深度代理 
      return reactive(res)
    }

    return res
  }
}
// 5. set 函数的代理:调用了trigger方法
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    const oldValue = (target as any)[key]
    if (!shallow) {
      // 如果value本身是响应对象,把他变成普通对象
      // 对应get中 isObject(res),方便统一处理
      // 这也是vue3与vue2 不同的地方
      // { person: {name:'tom'} }
      // vue2在代理的时候,两层都会代理
      // vue3在代理的时候,只代理第一层,在使用到person的时候才会代理第二层
      value = toRaw(value)
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
      
    const result = Reflect.set(target, key, value, receiver)
    // 防止原型链的影响--ppt
    if (target === toRaw(receiver)) {
        // 重点:在改变响应对象的值的时候,调用trigger 触发响应
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
    return result
  }
}

三、Effect的依赖收集与响应触发 (分-总-分-问题)

// 1. Effct 函数定义 与 局部变量缓存
export interface ReactiveEffect {
  (): T
  _isEffect: true
  id: number
  active: boolean // active是effect激活的开关,打开会收集依赖,关闭会导致收集依赖无效
  raw: () => T // 原始监听函数
  deps: Array // 存储依赖的deps
  options: ReactiveEffectOptions
  allowRecurse: boolean
}
type Dep = Set
type KeyToDepMap = Map


// 应用中 响应对象对应的 KeyToDepMap
const targetMap = new WeakMap()
// 当前执行的effect
let activeEffect: ReactiveEffect | undefined
// 执行中的effect栈
const effectStack: ReactiveEffect[] = []


// 2. Effct 
export function effect(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
  // fn已经是一个effect函数了,利用fn.raw重新创建effect
  if (isEffect(fn)) {
    fn = fn.raw
  }
  // 创建监听函数
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    effect()
  }
  return effect
}

// 3.createReactiveEffect
function createReactiveEffect(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect {
  const effect = function reactiveEffect(): unknown {
    // 防止在effect(() => {data.count++}),造成循环引用
    if (!effectStack.includes(effect)) {
      cleanup(effect) // effect.deps = []
      try {
        effectStack.push(effect)
        activeEffect = effect
        // 调用原始函数时,如果响应式数据取值了
        // 会触发这个响应式对象的getter,getter里面就会调用track方法收集依赖
        return fn() 
        
      } finally {
        effectStack.pop()
        // 指向最后一个effect: 
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}


// 4.track 函数:收集依赖
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
  }
}

// 5. trigger函数:触发依赖
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map | Set
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
  // 确定需要触发的依赖 set
  const effects = new Set()
  const add = (effectsToAdd: Set | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }
  
  if (key !== void 0) {
    add(depsMap.get(key))
  }
  
  console.log('count的Effects', effects)
  const run = (effect: ReactiveEffect) => {
    // 如果传入自定义调度器则执行自定义的,可以扩展effect执行
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}

你可能感兴趣的:(vue3响应式数据原理)