vue3響應式數(shù)據(jù)原理

Effect 原理解析 與 實現(xiàn)

引言:

vue、react 框架的核心都是數(shù)據(jù)驅動視圖也就是model => view,實現(xiàn)的核心也就是 數(shù)據(jù)響應。

主要就三步:

  1. 創(chuàng)建響應式的數(shù)據(jù) defineProperty、pxoxy。這樣使用、修改數(shù)據(jù)的事件我們都能捕捉到

  2. 在使用響應式的數(shù)據(jù)時收集依賴,把跟該數(shù)據(jù)相關的副作用都儲存起來

  3. 在修改響應式的數(shù)據(jù)時觸發(fā)依賴,執(zhí)行相關的副作用

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

一、effect:副作用函數(shù)

1.類似于vue2.0中watch 的升級版,如果函數(shù)中用到的響應式的數(shù)據(jù)發(fā)生了變化,則會執(zhí)行該函數(shù)

// Effect 的簡單應用
const component = defineComponent({
            name: 'zhenAPP',
            template: `
                <div>
                    <button @click="addHandler">add</button>
                </div>
            `,
            setup(props) {
                const data = reactive({
                    count: 0,
                });
                console.log('-----------創(chuàng)建reactive------------')
                console.log('創(chuàng)建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')
// 設置并運行帶副作用的渲染函數(shù)
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense ...)

二、proxy 與reflect

Object.defineProperty API 的一些缺點:

1. 不能監(jiān)聽對象屬性新增和刪除;
2. 初始化階段遞歸執(zhí)行 Object.defineProperty 帶來的性能負擔。

const p = new Proxy(target, handlerObject)

handlerObject = {
  get(target,key,receiver){ // receiver 可以理解成改變get函數(shù)中的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('創(chuàng)建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的實現(xiàn)

// 0 reactactive模塊的存儲變量:reactiveMap
//  作用1:維護整個應用數(shù)據(jù)代理,防止對同一個對象重復代理,比如父子組件共享某個響應數(shù)據(jù)
//  作用2:weakmap 本身的優(yōu)勢,這樣某個組件卸載之后,組件上的響應數(shù)據(jù)也會被刪除,reactiveMap也會刪除響應數(shù)據(jù)。防止內存泄露
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 函數(shù)的代理:調用了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 實現(xiàn)依賴收集。effect 中接下來會分析
    track(target, TrackOpTypes.GET, key)
    
    if (isObject(res)) {
      // 深度代理 
      return reactive(res)
    }

    return res
  }
}
// 5. set 函數(shù)的代理:調用了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),方便統(tǒng)一處理
      // 這也是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 觸發(fā)響應
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
    return result
  }
}

三、Effect的依賴收集與響應觸發(fā) (分-總-分-問題)

// 1. Effct 函數(shù)定義 與 局部變量緩存
export interface ReactiveEffect<T = any> {
  (): T
  _isEffect: true
  id: number
  active: boolean // active是effect激活的開關,打開會收集依賴,關閉會導致收集依賴無效
  raw: () => T // 原始監(jiān)聽函數(shù)
  deps: Array<Dep> // 存儲依賴的deps
  options: ReactiveEffectOptions
  allowRecurse: boolean
}
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>


// 應用中 響應對象對應的 KeyToDepMap
const targetMap = new WeakMap<any, KeyToDepMap>()
// 當前執(zhí)行的effect
let activeEffect: ReactiveEffect | undefined
// 執(zhí)行中的effect棧
const effectStack: ReactiveEffect[] = []


// 2. Effct 
export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  // fn已經(jīng)是一個effect函數(shù)了,利用fn.raw重新創(chuàng)建effect
  if (isEffect(fn)) {
    fn = fn.raw
  }
  // 創(chuàng)建監(jiān)聽函數(shù)
  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++}),造成循環(huán)引用
    if (!effectStack.includes(effect)) {
      cleanup(effect) // effect.deps = []
      try {
        effectStack.push(effect)
        activeEffect = effect
        // 調用原始函數(shù)時,如果響應式數(shù)據(jù)取值了
        // 會觸發(fā)這個響應式對象的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 函數(shù):收集依賴
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函數(shù):觸發(fā)依賴
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
  }
  // 確定需要觸發(fā)的依賴 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) => {
    // 如果傳入自定義調度器則執(zhí)行自定義的,可以擴展effect執(zhí)行
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容