Vue3.0 響應式系統源碼逐行講解

前言

關於響應式原理想必你們都很清楚了,下面我將會根據響應式API來具體講解Vue3.0中的實現原理, 另外我只會針對get,set進行深刻分析,本文包含如下API實現,推薦你們順序閱讀javascript

  • effect
  • reactive
  • readonly
  • computed
  • ref

對了,你們必定要先知道怎麼用哦~html

引子

先來段代碼,你們能夠直接複製哦,注意引用的文件vue

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="../packages/vue/dist/vue.global.js"></script>
</head>
<body>
  <div id="app"></div>
  <script>
    const { reactive, computed, effect, watch, createApp } = Vue
    const App = {
      template: `
        <div id="box">
            <button @click="increment">{{ state.count }}</button>
        </div> 
      `,
      setup() {
        const state = reactive({
          count: 0
        })
        function increment(e) {
          state.count++
        }
        effect(() => {
          console.log('count改變', state.count);
        })
        return {
          state,
          increment
        }
      }
    }
    createApp().mount(App, '#app')
  </script>
</body>
</html>
複製代碼

這段代碼,想必你們都看得懂,點擊後count增長,視圖也隨之更新,effect監聽了count改變,那麼爲何effect能觀察到count變化呢,還有爲何reactive能夠實現響應式?java

effect

爲何要先說這個函數呢,由於它和其餘函數都息息相關,只有先了解它才能更好的理解其餘響應式APInode

上源碼react

export function effect( fn: Function, options: ReactiveEffectOptions = EMPTY_OBJ ): ReactiveEffect {
  if ((fn as ReactiveEffect).isEffect) {
    fn = (fn as ReactiveEffect).raw
  }
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    effect()
  }
  return effect
}
複製代碼
  • if判斷,判斷若是傳入的fn函數,它已是effect了,也就是一個標識,直接獲取該函數上的raw屬性,這個屬性後面會講到api

  • 調用createReactiveEffect數組

  • 若是options中有lazy,就會當即調用effect,其實本質上調用的仍是傳入的fn函數數據結構

    // 瞭解一下options有哪些
    {
      lazy?: boolean  // 是否當即調用fn
      computed?: boolean  // 是不是computed
      scheduler?: (run: Function) => void  // 在調用fn以前執行
      onTrack?: (event: DebuggerEvent) => void // 在依賴收集完成以後調用
      onTrigger?: (event: DebuggerEvent) => void // 在調用fn以前執行,源碼上來看和scheduler調用時機同樣,只是傳入參數不一樣
      onStop?: () => void  // 清除依賴完成後調用
    }
    複製代碼
  • 返回effectapp

createReactiveEffect

上面提到了createReactiveEffect函數,咱們來看看它的實現

function createReactiveEffect( fn: Function, options: ReactiveEffectOptions ): ReactiveEffect {
  // 又包裝了一層函數
  const effect = function effect(...args): any {
    return run(effect as ReactiveEffect, fn, args)
  } as ReactiveEffect
  effect.isEffect = true  // 標識effect
  effect.active = true  // 若是active
  effect.raw = fn // 傳入的回調
  effect.scheduler = options.scheduler
  effect.onTrack = options.onTrack
  effect.onTrigger = options.onTrigger
  effect.onStop = options.onStop
  effect.computed = options.computed
  effect.deps = [] // 用於收集依賴
  return effect
}
複製代碼

注意,敲黑板,這裏有個run函數,很重要,由於它保存了依賴

function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
  if (!effect.active) {
    return fn(...args)
  }
  if (activeReactiveEffectStack.indexOf(effect) === -1) {
    cleanup(effect)
    try {
      activeReactiveEffectStack.push(effect)
      return fn(...args)
    } finally {
      activeReactiveEffectStack.pop()
    }
  }
}
複製代碼

他把依賴存儲在了一個全局的數組中activeReactiveEffectStack, 他以棧的形式存儲,調用完依賴後,會彈出,你們要留意一下這裏,後面會用到

怎麼樣,是否是很簡單~

reactive

export function reactive(target: object) {
  // 若是target是已經被readonly對象,那麼直接返回對應的proxy對象
  if (readonlyToRaw.has(target)) {
    return target
  }

  // 若是target是已經被readonly對象,那麼直接返回對應的真實對象
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}
複製代碼
  1. 前兩個if是用來處理這種狀況的
// 狀況一
const state1 = readonly({ count: 0 })
const state2 = reactive(state1)

// 狀況二
const obj = { count: 0 }
const state1 = readonly(obj)
const state2 = reactive(obj)
複製代碼

能夠看到reactive它的參數是被readonly的對象,reactive不會對它再次建立響應式,而是經過Map映射,拿到對應的對象,即Proxy <==> Object的相互轉換。

  1. createReactiveObject建立響應式對象,注意它的參數

    createReactiveObject(
        target,
        rawToReactive,    // Object ==> Proxy
        reactiveToRaw,    // Proxy ==> Object
        mutableHandlers,  // get set has ...
        mutableCollectionHandlers  // 不多會用,不講了~
    )
    複製代碼

以上就是reative一開始所作的一些事情,下面繼續分析createReactiveObject

createReactiveObject

function createReactiveObject( target: any, toProxy: WeakMap<any, any>, toRaw: WeakMap<any, any>, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
  // 若是不是對象,在開發環境報出警告
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }

  let observed = toProxy.get(target)
  // 若是目標對象已經有proxy對象,直接返回
  if (observed !== void 0) {
    return observed
  }

  // 若是目標對象是proxy的對象,而且有對應的真實對象,那麼也直接返回
  if (toRaw.has(target)) {
    return target
  }
  // 若是它是vnode或者vue,則不能被觀測
  if (!canObserve(target)) {
    return target
  }
  // 判斷被觀測的對象是不是set,weakSet,map,weakMap,根據狀況使用對應proxy的,配置對象
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
  observed = new Proxy(target, handlers)
  toProxy.set(target, observed)
  toRaw.set(observed, target)
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map())
  }
  return observed
}
複製代碼
  1. 第一個if,判斷是不是對象,不然報出警告

  2. toProxy拿到觀測對象的Proxy對象,若是存在直接返回

    // 這種狀況
    const obj = { count: 0 }
    const state1 = reative(obj)
    const state2 = reative(obj)
    複製代碼
  3. toRaw拿到Proxy對象對應的真實對象,若是存在直接返回

    // 這種狀況
    const obj = { count: 0 }
    const state1 = reative(obj)
    const state2 = reative(state1)
    複製代碼
  4. 有些狀況沒法被觀測,則直接返回觀測對象自己

    const canObserve = (value: any): boolean => {
      return (
        !value._isVue &&
        !value._isVNode &&
        observableValueRE.test(toTypeString(value)) &&
        !nonReactiveValues.has(value)
      )
    }
    複製代碼
  5. 設置handlers,即get,set等屬性訪問器, 注意:collectionHandlers是用來處理觀測對象爲Set,Map等狀況,不多見,這裏就不講了

    const handlers = collectionTypes.has(target.constructor)
        ? collectionHandlers
        : baseHandlers
    
    複製代碼
  6. 而後建立了Proxy對象,並把觀測對象和Proxy對象,分別作映射

    observed = new Proxy(target, handlers)
      toProxy.set(target, observed)
      toRaw.set(observed, target)
    複製代碼
  7. 而後在targetMap作了target ==> Map的映射,這又是幹嗎,注意:targetMap是全局的

    export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
      if (!targetMap.has(target)) {
        targetMap.set(target, new Map())
      }
    複製代碼

    在這裏先給你們賣個關子,targetMap很是重要,是用來保存依賴的地方

    講完了reactive,能夠回到一開始的引子

依賴收集

說到依賴收集,不得不提到,依賴的建立,那麼Vue3.0是在哪裏建立了渲染依賴呢,你們能夠找到下面這段代碼以及文件

// vue-next\packages\runtime-core\src\createRenderer.ts
  function setupRenderEffect( instance: ComponentInternalInstance, parentSuspense: HostSuspsenseBoundary | null, initialVNode: HostVNode, container: HostElement, anchor: HostNode | null, isSVG: boolean ) {
    // create reactive effect for rendering
    let mounted = false
    instance.update = effect(function componentEffect() {
	// ...
    }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
  }
複製代碼

代碼特別長,我剪掉了中間部分,你們還記得effect有個選項lazy嗎,沒錯,它默認是false,也就會當即調用傳入的componentEffect回調,在它內部調用了patch實現了組件的掛載。

敲黑板,關鍵來了,還記得effect調用,內部會調用run方法嗎

function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
  if (!effect.active) {
    return fn(...args)
  }
  if (activeReactiveEffectStack.indexOf(effect) === -1) {
    cleanup(effect)
    try {
      activeReactiveEffectStack.push(effect)
      return fn(...args)
    } finally {
      activeReactiveEffectStack.pop()
    }
  }
}
複製代碼

這裏進行了第一步的依賴收集,保存在全局數組中,爲了方便觸發get的對象,將依賴收集到本身的deps

而後就是調用patch,進行組件掛載

if (!mounted) {
    const subTree = (instance.subTree = renderComponentRoot(instance))
    // beforeMount hook
    if (instance.bm !== null) {
        invokeHooks(instance.bm)
    }
    patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
    initialVNode.el = subTree.el
    // mounted hook
    if (instance.m !== null) {
        queuePostRenderEffect(instance.m, parentSuspense)
    }
    mounted = true
}
複製代碼

至於它內部實現,我就不講了,不是本文重點,而後咱們去編譯的地方看看

//vue-next\packages\runtime-core\src\component.ts
function finishComponentSetup( instance: ComponentInternalInstance, parentSuspense: SuspenseBoundary | null ) {
  const Component = instance.type as ComponentOptions
  if (!instance.render) {
    if (Component.template && !Component.render) {
      if (compile) {
        Component.render = compile(Component.template, {
          onError(err) {}
        })
      } else if (__DEV__) {
        warn(
          `Component provides template but the build of Vue you are running ` +
            `does not support on-the-fly template compilation. Either use the ` +
            `full build or pre-compile the template using Vue CLI.`
        )
      }
    }
    if (__DEV__ && !Component.render) {
      warn(
        `Component is missing render function. Either provide a template or ` +
          `return a render function from setup().`
      )
    }
    instance.render = (Component.render || NOOP) as RenderFunction
  }

  // ...其餘
}
複製代碼

上面的代碼是編譯部分,咱們來看看例子中編譯後是什麼樣

(function anonymous( ) {
const _Vue = Vue
const _createVNode = Vue.createVNode

const _hoisted_1 = { id: "box" }

return function render() {
  with (this) {
    const { toString: _toString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
    
    return (_openBlock(), _createBlock("div", _hoisted_1, [
      _createVNode("button", { onClick: increment }, _toString(state.count), 9 /* TEXT, PROPS */, ["onClick"])
    ]))
  }
}
})
複製代碼

能夠看到,編譯的代碼中,有使用到state.count,那麼就會觸發get訪問器,從而收集依賴,至於爲何能直接訪問到屬性,緣由是因爲with設置了上下文,下面咱們具體分析get

get

// vue-next\packages\reactivity\src\baseHandlers.ts
function createGetter(isReadonly: boolean) {
  return function get(target: any, key: string | symbol, receiver: any) {
    const res = Reflect.get(target, key, receiver)
    if (typeof key === 'symbol' && builtInSymbols.has(key)) {
      return res
    }
    // _isRef
    if (isRef(res)) {
      return res.value
    }
    track(target, OperationTypes.GET, key)
    // 若是該屬性對應的值仍是對象,就繼續遞歸建立響應式
    return isObject(res)
      ? isReadonly
        ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          readonly(res)
        : reactive(res)
      : res
  }
}
複製代碼
  1. 調用Reflect.get獲取屬性值

  2. 若是keysymbol而且是Symbol的一個屬性,就直接返回該值

    // 這種狀況
    const key = Symbol('key')
    const state = reative({
        [key]: 'symbol value'
    })
    state[key]
    複製代碼
  3. 若是值爲Ref返回該值的value,看到這裏若是你們有了解過ref api的話就知道了,因爲ref它本身實現了本身的get,set,因此再也不須要執行後面的邏輯,這個在後面會講

  4. 調用track

  5. 遞歸深度觀測,使整個對象都爲響應式

    下面我會詳細講解

track

在講它以前,先了解它有哪些參數

target: any,  // 目標對象
  type: OperationTypes,  // 追蹤數據變化類型,這裏是get
  key?: string | symbol // 須要獲取的key
  export const enum OperationTypes {
      SET = 'set',
      ADD = 'add',
      DELETE = 'delete',
      CLEAR = 'clear',
      GET = 'get',
      HAS = 'has',
      ITERATE = 'iterate' 
  }

複製代碼
export function track( target: any, type: OperationTypes, key?: string | symbol ) {
  if (!shouldTrack) {
    return
  }
  // 獲取activeReactiveEffectStack中的依賴
  const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  if (effect) {
    if (type === OperationTypes.ITERATE) {
      key = ITERATE_KEY
    }
    // 獲取目標對象對應的依賴map
    let depsMap = targetMap.get(target)
    if (depsMap === void 0) {
      targetMap.set(target, (depsMap = new Map()))
    }
    // 獲取對應屬性的依賴
    let dep = depsMap.get(key as string | symbol)
    // 若是該依賴不存在
    if (!dep) {
      // 設置屬性對應依賴
      depsMap.set(key as string | symbol, (dep = new Set()))
    }
    // 若是屬性對應依賴set中不存在該依賴
    if (!dep.has(effect)) {
      // 添加到依賴set中
      dep.add(effect)
      effect.deps.push(dep)
      if (__DEV__ && effect.onTrack) {
        // 調用onTrack鉤子
        effect.onTrack({
          effect,
          target,
          type,
          key
        })
      }
    }
  }
}
複製代碼
  1. activeReactiveEffectStack我兩次提到,從它這裏拿到了依賴,注意後面執行完依賴後,會從它裏面彈出

  2. 若是effect存在

    • targetMap中獲取對象,對飲的Map,具體的數據結構相似這樣

      const state = reative({
          count: 0
      })
      effect(() => {
        console.log(state.count)  
      }) 
      
      // 依賴大體結構(隨便寫的,不太規範)
      {
          target(state):Map {
              count: Set (componentEffect渲染依賴, user本身添加的依賴)
          }
      }
      複製代碼
    • 若是該對象不存在Map,就初始化一個

    • 若是該Map中屬性對應的Set不存在,就初始化一個Set

    • 添加依賴到Set

    • 添加依賴到effect自身的deps數組中

    • 最後調用onTrack回調

      // 調用onTrack鉤子
      effect.onTrack({
          effect,
          target,
          type,
          key
      })
      複製代碼

    OK,Track實現大致就這樣,是否是也很簡單,有了這些基礎,後面要講的一些API就很容易理解了

set

當咱們點擊按鈕後,就會觸發set屬性訪問器

function set( target: any, key: string | symbol, value: any, receiver: any ): boolean {
  value = toRaw(value)
  const hadKey = hasOwn(target, key)
  const oldValue = target[key]
  // 若是舊的值是ref,而新的值不是ref
  if (isRef(oldValue) && !isRef(value)) {
    // 直接更改原始ref便可
    oldValue.value = value
    return true
  }
  const result = Reflect.set(target, key, value, receiver)
  // don't trigger if target is something up in the prototype chain of original
  if (target === toRaw(receiver)) {
    /* istanbul ignore else */
    if (__DEV__) {
      const extraInfo = { oldValue, newValue: value }
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key, extraInfo)
      } else if (value !== oldValue) {
        trigger(target, OperationTypes.SET, key, extraInfo)
      }
    } else {
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key)
      } else if (value !== oldValue) {
        trigger(target, OperationTypes.SET, key)
      }
    }
  }
  return result
}
複製代碼
  1. 判斷舊值是ref,新值不是ref

    // 這種狀況
    const val = ref(0)
    const state = reative({
        count: val
    })
    state.count = 1
    // 其實state.count最終仍是ref,仍是能經過value訪問
    state.count.value  // 1
    複製代碼
  2. 調用Reflect.set修改值

  3. 開發環境下,拿到新舊值組成的對象,調用trigger,爲何開發環境要這麼作呢,實際上是爲了方便onTrigger能拿到新舊值

    trigger(target, OperationTypes.ADD, key, extraInfo)
    複製代碼

    能夠看到第二個參數和track是同樣的enum,有兩種狀況,一種咱們設置了新的屬性和值,另外一種修改了原有屬性值,下面咱們來看看trigger實現。

trigger

export function trigger( target: any, type: OperationTypes, key?: string | symbol, extraInfo?: any ) {
  const depsMap = targetMap.get(target)
  if (depsMap === void 0) {
    // never been tracked
    return
  }
  // effect set
  const effects: Set<ReactiveEffect> = new Set()
  // computed effect set
  const computedRunners: Set<ReactiveEffect> = new Set()

  if (type === OperationTypes.CLEAR) {
    depsMap.forEach(dep => {
      addRunners(effects, computedRunners, dep)
    })
  } else {
    // 添加effect到set中
    if (key !== void 0) {
      addRunners(effects, computedRunners, depsMap.get(key as string | symbol))
    }
    // also run for iteration key on ADD | DELETE
    if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
      const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
      addRunners(effects, computedRunners, depsMap.get(iterationKey))
    }
  }

  // 執行set中的effect
  const run = (effect: ReactiveEffect) => {
    scheduleRun(effect, target, type, key, extraInfo)
  }
  computedRunners.forEach(run)
  effects.forEach(run)
}
複製代碼

看到這個函數開始的targetMap,你們應該很清楚要幹嗎了吧,沒錯,拿到對象的Map,它包含了屬性的全部依賴

  1. 若是沒有Map直接返回

  2. 建立了兩個Set,要幹嗎用呢

    // 用來保存將要執行的依賴
      const effects: Set<ReactiveEffect> = new Set()
      // computed依賴,由於trigger不只是要處理effect,watch,還要處理computed惰性求值的狀況
      const computedRunners: Set<ReactiveEffect> = new Set()
    複製代碼
  3. 處理三種狀況CLEAR,ADD,DELETE,SET(這裏沒有標識)

    // effect set
    const effects: Set<ReactiveEffect> = new Set()
    // computed effect set
    const computedRunners: Set<ReactiveEffect> = new Set()
    
    function addRunners( effects: Set<ReactiveEffect>, computedRunners: Set<ReactiveEffect>, effectsToAdd: Set<ReactiveEffect> | undefined ) {
      if (effectsToAdd !== void 0) {
        effectsToAdd.forEach(effect => {
          if (effect.computed) {
            computedRunners.add(effect)
          } else {
            effects.add(effect)
          }
        })
      }
    }
    複製代碼

    能夠看到,三種狀況實際上都差很少,惟一的區別就是,若是添加的對象是數組,就會拿到length屬性的依賴,用於修改數組長度

    if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
        const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
        addRunners(effects, computedRunners, depsMap.get(iterationKey))
    }
    複製代碼
  4. 執行屬性對應的依賴

    // 執行set中的effect
      const run = (effect: ReactiveEffect) => {
        scheduleRun(effect, target, type, key, extraInfo)
      }
    
      computedRunners.forEach(run)
      effects.forEach(run)
    複製代碼
    function scheduleRun( effect: ReactiveEffect, target: any, type: OperationTypes, key: string | symbol | undefined, extraInfo: any ) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(
          extend(
            {
              effect,
              target,
              key,
              type
            },
            extraInfo  // { oldValue, newValue: value }
          )
        )
      }
      if (effect.scheduler !== void 0) {
        effect.scheduler(effect)
      } else {
        effect()
      }
    }
    
    複製代碼

    最後調用了scheduleRun,它內部會分別執行onTrigger,schedulereffect

    須要注意的是,只有開發環境纔會執行onTrigger,這也是爲何,前面要這麼判斷

    if (__DEV__) {
        const extraInfo = { oldValue, newValue: value }
        if (!hadKey) {
            trigger(target, OperationTypes.ADD, key, extraInfo)
        } else if (value !== oldValue) {
            trigger(target, OperationTypes.SET, key, extraInfo)
        }
    } 
    複製代碼

readonly

有了前面的基礎,readonly看起來會很是簡單,惟一的區別就是rawToReadonly,rawToReadonly, readonlyHandlers

export function readonly(target: object) {

  if (reactiveToRaw.has(target)) {
    target = reactiveToRaw.get(target)
  }
  return createReactiveObject(
    target,
    rawToReadonly,
    readonlyToRaw,
    readonlyHandlers,
    readonlyCollectionHandlers
  )
}
複製代碼

前兩個你們應該能猜出來了,關鍵是最後這個readonlyHandlers,區別就在set

set(target: any, key: string | symbol, value: any, receiver: any): boolean {
   if (LOCKED) {
     if (__DEV__) {
       console.warn(
         `Set operation on key "${key as any}" failed: target is readonly.`,
         target
       )
     }
     return true
   } else {
     return set(target, key, value, receiver)
   }
 }
複製代碼

它的實現很簡單,不過LOCKED有是什麼鬼,你們能夠找到lock.ts

//vue-next\packages\reactivity\src\lock.ts
export let LOCKED = true

export function lock() {
  LOCKED = true
}

export function unlock() {
  LOCKED = false
}

複製代碼

看似簡單,可是卻很是重要,它可以控制被readonly的對象可以暫時被更改,就好比咱們經常使用的props,它是沒法被修改的,可是Vue內部又要對他進行更新,那怎麼辦,話很少說,咱們再源碼中看他具體應用

// vue-next\packages\runtime-core\src\componentProps.ts
export function resolveProps( instance: ComponentInternalInstance, rawProps: any, _options: ComponentPropsOptions | void ) {
  const hasDeclaredProps = _options != null
  const options = normalizePropsOptions(_options) as NormalizedPropsOptions
  if (!rawProps && !hasDeclaredProps) {
    return
  }
  const props: any = {}
  let attrs: any = void 0

  const propsProxy = instance.propsProxy
  const setProp = propsProxy
    ? (key: string, val: any) => {
        props[key] = val
        propsProxy[key] = val
      }
    : (key: string, val: any) => {
        props[key] = val
      }

  unlock()
  
  // 省略一些修改props操做。。
    
  lock()

  instance.props = __DEV__ ? readonly(props) : props
  instance.attrs = options
    ? __DEV__ && attrs != null
      ? readonly(attrs)
      : attrs
    : instance.props
}
複製代碼

這裏先後分別調用了unlocklock,這樣就能夠控制對readonly屬性的修改

那麼readonly的講解就到這了

computed

export function computed<T>( getterOrOptions: (() => T) | WritableComputedOptions<T> ): any {
  const isReadonly = isFunction(getterOrOptions)
  const getter = isReadonly
    ? (getterOrOptions as (() => T))
    : (getterOrOptions as WritableComputedOptions<T>).get
  const setter = isReadonly
    ? null
    : (getterOrOptions as WritableComputedOptions<T>).set

  let dirty: boolean = true
  let value: any = undefined

  const runner = effect(getter, {
    lazy: true,
    computed: true,
    scheduler: () => {
      dirty = true
    }
  })
  return {
    _isRef: true,
    // expose effect so computed can be stopped
    effect: runner,
    get value() {
      if (dirty) {
        value = runner()
        dirty = false
      }
      trackChildRun(runner)
      return value
    },
    set value(newValue) {
      if (setter) {
        setter(newValue)
      } else {
        // TODO warn attempting to mutate readonly computed value
      }
    }
  }
}
複製代碼

首先是前面這段

const isReadonly = isFunction(getterOrOptions)
  const getter = isReadonly
    ? (getterOrOptions as (() => T))
    : (getterOrOptions as WritableComputedOptions<T>).get
  const setter = isReadonly
    ? null
    : (getterOrOptions as WritableComputedOptions<T>).set
複製代碼

你們都知道computed是能夠單獨寫一個函數,或者get,set訪問的,這裏很少講

而後調用了effect,這裏lazy設置爲true, scheduler能夠更改dirty爲true

const runner = effect(getter, {
    lazy: true,
    computed: true,
    scheduler: () => {
        dirty = true
    }
})
複製代碼

而後咱們具體來看看,返回的對象

{
    _isRef: true,
    // expose effect so computed can be stopped
    effect: runner,
    get value() {
      if (dirty) {
        value = runner()
        dirty = false
      }
      trackChildRun(runner)
      return value
    },
    set value(newValue) {
      if (setter) {
        setter(newValue)
      } else {
        // TODO warn attempting to mutate readonly computed value
      }
    }
  }
複製代碼

先說說set吧,尤大彷佛還沒寫完,只是單純能修改值

而後是get,注意dirty的變化,若是computed依賴了state中的值,初次渲染時,他會調用依賴,而後dirty = false,關鍵來了,最後執行了trackChildRun

function trackChildRun(childRunner: ReactiveEffect) {
  const parentRunner =
    activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  if (parentRunner) {
    for (let i = 0; i < childRunner.deps.length; i++) {
      const dep = childRunner.deps[i]
      if (!dep.has(parentRunner)) {
        dep.add(parentRunner)
        parentRunner.deps.push(dep)
      }
    }
  }
}

複製代碼

因爲computed是依賴了state中的屬性的,一旦在初始時觸發了get,執行runner,就會將依賴收集到activeReactiveEffectStack中,最後纔是本身的依賴,棧的頂部是state屬性的依賴

if (!dep.has(parentRunner)) {
    dep.add(parentRunner)
    parentRunner.deps.push(dep)
}
複製代碼

因此最後這段代碼實現了state屬性變化後,才致使了computed依賴的調用,從而惰性求值

ref

const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
export function ref<T>(raw: T): Ref<T> {
  raw = convert(raw)
  const v = {
    _isRef: true,
    get value() {
      track(v, OperationTypes.GET, '')
      return raw
    },
    set value(newVal) {
      raw = convert(newVal)
      trigger(v, OperationTypes.SET, '')
    }
  }
  return v as Ref<T>
}
複製代碼

ref的實現真的很簡單了,前面已經學習了那麼多,相信你們都能看懂了,區別就是convert(raw)對傳入的值進行了簡單判斷,若是是對象就設置爲響應式,不然返回原始值。

最後

終於分析完了,Vue3.0響應系統使用了Proxy相比於Vue2.0的代碼真的簡潔許多,也好理解,說難不難。其實還有watch並無講,它沒有在reactivity中,可是實現仍是使用了effect,套路都是同樣的。最後謝謝你們觀看。

相關文章
相關標籤/搜索