美文网首页
vue3响应式数据原理

vue3响应式数据原理

作者: 调皮的绅士 | 来源:发表于2021-11-26 23:43 被阅读0次

    Effect 原理解析 与 实现

    引言:

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

    主要就三步:

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

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

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

      <template>
      <div>{{msg}}</div>
      </template>
      <script>
      export default {
      data(){
      return {
      msg: 'hello world'
      }
      },
      methods: {
      change(){
      this.msg = 'zhenganlin'
      }
      }
      }
      </script>

    一、effect:副作用函数

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

    // Effect 的简单应用
    const component = defineComponent({
                name: 'zhenAPP',
                template: `
                    <div>
                        <button @click="addHandler">add</button>
                    </div>
                `,
                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
        <!DOCTYPE html>
        <html>
        <head>
            <title>vue-demo</title>
        </head>
        <body>
            <div id="app"></div>
            <script src="./packages/vue/dist/vue.global.js"></script>
            <script>
                const { defineComponent, createApp, reactive, toRefs, watchEffect } = Vue;
                console.log(Vue)
                const component = defineComponent({
                    template: `
                        <div>
                            {{ data.count }}
                            <button @click="addHandler">add</button>
                        </div>
                    `,
                    setup(props) {
                        const data = reactive({
                            count: 0,
                            person: {
                                name: 'zhenganlin'
                            }
                        });
                        console.log('创建reactive: ', data)
                        const addHandler = () => {
                            data.count++;
                            data.person = {age:25}
                        };
                        // watchEffect(() => {
                        //     console.log(data.count)
                        // });
                        return {
                            data,
                            addHandler,
                        };
                    },
                });
                createApp(component).mount(document.querySelector('#app'));
            </script>
        </body>
        </html>
    
    

    三、响应式api reactive的实现

    // 0 reactactive模块的存储变量:reactiveMap
    //  作用1:维护整个应用数据代理,防止对同一个对象重复代理,比如父子组件共享某个响应数据
    //  作用2:weakmap 本身的优势,这样某个组件卸载之后,组件上的响应数据也会被删除,reactiveMap也会删除响应数据。防止内存泄露
    export const reactiveMap = new WeakMap<Target, any>()
    
    // 1.reactive 方法
    export function reactive(target: object) {
      // ...排除不能代理的一些情况
      return createReactiveObject(
        target,
        mutableHandlers,
      )
    }
    
    // 2.createReactiveObject proxy代理
    function createReactiveObject(
      target: Target,
      baseHandlers: ProxyHandler<any>,
    ) {
     
      // 代理去重 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 = any> {
      (): T
      _isEffect: true
      id: number
      active: boolean // active是effect激活的开关,打开会收集依赖,关闭会导致收集依赖无效
      raw: () => T // 原始监听函数
      deps: Array<Dep> // 存储依赖的deps
      options: ReactiveEffectOptions
      allowRecurse: boolean
    }
    type Dep = Set<ReactiveEffect>
    type KeyToDepMap = Map<any, Dep>
    
    
    // 应用中 响应对象对应的 KeyToDepMap
    const targetMap = new WeakMap<any, KeyToDepMap>()
    // 当前执行的effect
    let activeEffect: ReactiveEffect | undefined
    // 执行中的effect栈
    const effectStack: ReactiveEffect[] = []
    
    
    // 2. Effct 
    export function effect<T = any>(
      fn: () => T,
      options: ReactiveEffectOptions = EMPTY_OBJ
    ): ReactiveEffect<T> {
      // 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<T = any>(
      fn: () => T,
      options: ReactiveEffectOptions
    ): ReactiveEffect<T> {
      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<unknown, unknown> | Set<unknown>
    ) {
      const depsMap = targetMap.get(target)
      if (!depsMap) {
        // never been tracked
        return
      }
      // 确定需要触发的依赖 set
      const effects = new Set<ReactiveEffect>()
      const add = (effectsToAdd: Set<ReactiveEffect> | 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响应式数据原理

          本文链接:https://www.haomeiwen.com/subject/ghkfxrtx.html