【Vue3.0】- 響應(yīng)式

響應(yīng)式原理

  • 響應(yīng)式是 Vue.js 組件化更新渲染的一個核心機制

Vue2.x響應(yīng)式實現(xiàn)

  • Object.defineProperty API 劫持數(shù)據(jù)的變化
  • 在數(shù)據(jù)被訪問的時候收集依賴
  • 然后在數(shù)據(jù)被修改的時候通知依賴更新
  • Vue.js 2.x 中,Watcher 就是依賴,
    • 首先是依賴收集流程,組件在 render 的時候會訪問模板中的數(shù)據(jù),觸發(fā) getterrender watcher 作為依賴收集,并和數(shù)據(jù)建立聯(lián)系
    • 然后是派發(fā)通知流程,當我對這些數(shù)據(jù)修改的時候,會觸發(fā) setter,通知 render watcher 更新,進而觸發(fā)了組件的重新渲染
  • Object.defineProperty API 的一些缺點:
    • 不能監(jiān)聽對象屬性新增和刪除
    • 初始化階段遞歸執(zhí)行 Object.defineProperty 帶來的性能負擔(dān)

響應(yīng)式對象的實現(xiàn)差異

  • Vue.js 2.x 中構(gòu)建組件時,只要我們在 dataprops、computed 中定義數(shù)據(jù),那么它就是響應(yīng)式的
  • 到了 Vue.js 3.0 構(gòu)建組件時,你可以不依賴于 Options API,而使用 Composition API 去編寫
  • Composition API 更推薦用戶主動定義響應(yīng)式對象,而非內(nèi)部的黑盒處理

Reactive API

  • reactive 函數(shù)的具體實現(xiàn)過程
function reactive (target) {
   // 如果嘗試把一個 readonly proxy 變成響應(yīng)式,直接返回這個 readonly proxy
  if (target && target.__v_isReadonly) {
     return target
  } 
  return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
}
  • reactive 內(nèi)部通過 createReactiveObject 函數(shù)把 target 變成了一個響應(yīng)式對象
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
  if (!isObject(target)) {
    // 目標必須是對象或數(shù)組類型
    if ((process.env.NODE_ENV !== 'production')) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
    // target 已經(jīng)是 Proxy 對象,直接返回
    // 有個例外,如果是 readonly 作用于一個響應(yīng)式對象,則繼續(xù)
    return target
  }
  if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) {
    // target 已經(jīng)有對應(yīng)的 Proxy 了
    return isReadonly ? target.__v_readonly : target.__v_reactive
  }
  // 只有在白名單里的數(shù)據(jù)類型才能變成響應(yīng)式
  if (!canObserve(target)) {
    return target
  }
  // 利用 Proxy 創(chuàng)建響應(yīng)式
  const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)
  // 給原始數(shù)據(jù)打個標識,說明它已經(jīng)變成響應(yīng)式,并且有對應(yīng)的 Proxy 了
  def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
  return observed
}
  • 1、 函數(shù)首先判斷 target 是不是數(shù)組或者對象類型,如果不是則直接返回。所以原始數(shù)據(jù) target 必須是對象或者數(shù)組。
  • 2、通過 target.__v_raw 屬性,和__v_isReactive屬性來判斷 target 是否已經(jīng)是一個響應(yīng)式對象,如果是,直接返回該對象
  • 3、使用 canObserve 函數(shù)對 target 對象做一進步限制
    • 帶有 __v_skip 屬性的對象、被凍結(jié)的對象,以及不在白名單內(nèi)的對象如 Date 類型的對象實例是不能變成響應(yīng)式的
const canObserve = (value) => {
  return (!value.__v_skip &&
   isObservableType(toRawType(value)) &&
   !Object.isFrozen(value))
}
const isObservableType = /*#__PURE__*/
makeMap('Object,Array,Map,Set,WeakMap,WeakSet')
  • 4、通過 Proxy API 劫持 target 對象,把它變成響應(yīng)式
  • 5、給原始數(shù)據(jù)打個標識,target.__v_reactive = observed
def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
Proxy 處理器對象 mutableHandlers
const mutableHandlers = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
  • 劫持了我們對 observed 對象的一些操作
  • 1)訪問對象屬性會觸發(fā) get 函數(shù);
  • 2)設(shè)置對象屬性會觸發(fā) set 函數(shù);
  • 3)刪除對象屬性會觸發(fā) deleteProperty 函數(shù);
  • 4)in 操作符會觸發(fā) has 函數(shù);
  • 5)通過 Object.getOwnPropertyNames 訪問對象屬性名會觸發(fā) ownKeys 函數(shù)
依賴收集:get 函數(shù)
  • 依賴收集發(fā)生在數(shù)據(jù)訪問的階段,get執(zhí)行createGetter
function createGetter(isReadonly = false) {
  return function get(target, key, receiver) {
    if (key === "__v_isReactive" /* isReactive */) {
      // 代理 observed.__v_isReactive
      return !isReadonly
    }
    else if (key === "__v_isReadonly" /* isReadonly */) {
      // 代理 observed.__v_isReadonly
      return isReadonly;
    }
    else if (key === "__v_raw" /* raw */) {
      // 代理 observed.__v_raw
      return target
    }
    const targetIsArray = isArray(target)
    // arrayInstrumentations 包含對數(shù)組一些方法修改的函數(shù)
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // 求值
    const res = Reflect.get(target, key, receiver)
    // 內(nèi)置 Symbol key 不需要依賴收集
    if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') {
      return res
    }
    // 依賴收集
    !isReadonly && track(target, "get" /* GET */, key)
    return isObject(res)
      ? isReadonly
        ?
        readonly(res)
        // 如果 res 是個對象或者數(shù)組類型,則遞歸執(zhí)行 reactive 函數(shù)把 res 變成響應(yīng)式
        : reactive(res)
      : res
  }
}
  • get 函數(shù)主要做了四件事情
  • 1)首先對特殊的 key 做了代理
  • 2)通過 Reflect.get 方法求值,如果 target 是數(shù)組且 key 命中了 arrayInstrumentations,則執(zhí)行對應(yīng)的函數(shù)
const arrayInstrumentations = {}
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
  arrayInstrumentations[key] = function (...args) {
    // toRaw 可以把響應(yīng)式對象轉(zhuǎn)成原始數(shù)據(jù)
    const arr = toRaw(this)
    for (let i = 0, l = this.length; i < l; i++) {
      // 依賴收集
      track(arr, "get" /* GET */, i + '')
    }
    // 先嘗試用參數(shù)本身,可能是響應(yīng)式數(shù)據(jù)
    const res = arr[key](...args)
    if (res === -1 || res === false) {
      // 如果失敗,再嘗試把參數(shù)轉(zhuǎn)成原始數(shù)據(jù)
      return arr[key](...args.map(toRaw))
    }
    else {
      return res
    }
  }
})
  • 3)通過 Reflect.get 求值,然后會執(zhí)行 track 函數(shù)收集依賴
  • 4)對計算的值 res 進行判斷,如果它也是數(shù)組或?qū)ο螅瑒t遞歸執(zhí)行 reactiveres 變成響應(yīng)式對象,因為Proxy劫持的是對象本身,并不能劫持子對象的變化
track函數(shù)收集依賴
  • 整個 get 函數(shù)最核心的部分其實是執(zhí)行 track 函數(shù)收集依賴
// 是否應(yīng)該收集依賴
let shouldTrack = true
// 當前激活的 effect
let activeEffect
// 原始數(shù)據(jù)對象 map
const targetMap = new WeakMap()
function track(target, type, key) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 每個 target 對應(yīng)一個 depsMap
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    // 每個 key 對應(yīng)一個 dep 集合
    depsMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) {
    // 收集當前激活的 effect 作為依賴
    dep.add(activeEffect)
   // 當前激活的 effect 收集 dep 集合作為依賴
    activeEffect.deps.push(dep)
  }
}
  • 收集的依賴就是數(shù)據(jù)變化后執(zhí)行的副作用函數(shù)
  • 創(chuàng)建了全局的 targetMap 作為原始數(shù)據(jù)對象的 Map,它的鍵是 target,值是 depsMap,作為依賴的 Map
  • depsMap 的鍵是 targetkey,值是 dep 集合
  • dep 集合中存儲的是依賴的副作用函數(shù)
派發(fā)通知:set 函數(shù)
  • 派發(fā)通知發(fā)生在數(shù)據(jù)更新的階段, set 函數(shù)的實現(xiàn),它是執(zhí)行 createSetter 函數(shù)的返回值
function createSetter() {
  return function set(target, key, value, receiver) {
    const oldValue = target[key]
    value = toRaw(value)
    const hadKey = hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // 如果目標的原型鏈也是一個 proxy,通過 Reflect.set 修改原型鏈上的屬性會再次觸發(fā) setter,這種情況下就沒必要觸發(fā)兩次 trigger 了
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, "add" /* ADD */, key, value)
      }
      else if (hasChanged(value, oldValue)) {
        trigger(target, "set" /* SET */, key, value, oldValue)
      }
    }
    return result
  }
}
  • 主要做兩件事情:
  • 1)通過 Reflect.set 求值
  • 2)通過 trigger 函數(shù)派發(fā)通知,并依據(jù) key 是否存在于 target 上來確定通知類型,即新增還是修改
trigger 函數(shù)派發(fā)通知
// 原始數(shù)據(jù)對象 map
const targetMap = new WeakMap()
function trigger(target, type, key, newValue) {
  // 通過 targetMap 拿到 target 對應(yīng)的依賴集合
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // 沒有依賴,直接返回
    return
  }
  // 創(chuàng)建運行的 effects 集合
  const effects = new Set()
  // 添加 effects 的函數(shù)
  const add = (effectsToAdd) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        effects.add(effect)
      })
    }
  }
  // SET | ADD | DELETE 操作之一,添加對應(yīng)的 effects
  if (key !== void 0) {
    add(depsMap.get(key))
  }
  const run = (effect) => {
    // 調(diào)度執(zhí)行
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    }
    else {
      // 直接運行
      effect()
    }
  }
  // 遍歷執(zhí)行 effects
  effects.forEach(run)
}
  • 主要做了四件事情
  • 1)通過 targetMap 拿到 target 對應(yīng)的依賴集合 depsMap
  • 2)創(chuàng)建運行的 effects 集合
  • 3)根據(jù) keydepsMap 中找到對應(yīng)的 effects 添加到 effects 集合;
  • 4)遍歷 effects 執(zhí)行相關(guān)的副作用函數(shù)
副作用函數(shù)
// 全局 effect 棧
const effectStack = []
// 當前激活的 effect
let activeEffect
function effect(fn, options = EMPTY_OBJ) {
  if (isEffect(fn)) {
    // 如果 fn 已經(jīng)是一個 effect 函數(shù)了,則指向原始函數(shù)
    fn = fn.raw
  }
  // 創(chuàng)建一個 wrapper,它是一個響應(yīng)式的副作用的函數(shù)
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    // lazy 配置,計算屬性會用到,非 lazy 則直接執(zhí)行一次
    effect()
  }
  return effect
}
function createReactiveEffect(fn, options) {
  const effect = function reactiveEffect(...args) {
    if (!effect.active) {
      // 非激活狀態(tài),則判斷如果非調(diào)度執(zhí)行,則直接執(zhí)行原始函數(shù)。
      return options.scheduler ? undefined : fn(...args)
    }
    if (!effectStack.includes(effect)) {
      // 清空 effect 引用的依賴
      cleanup(effect)
      try {
        // 開啟全局 shouldTrack,允許依賴收集
        enableTracking()
        // 壓棧
        effectStack.push(effect)
        activeEffect = effect
        // 執(zhí)行原始函數(shù)
        return fn(...args)
      }
      finally {
        // 出棧
        effectStack.pop()
        // 恢復(fù) shouldTrack 開啟之前的狀態(tài)
        resetTracking()
        // 指向棧最后一個 effect
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  }
  effect.id = uid++
  // 標識是一個 effect 函數(shù)
  effect._isEffect = true
  // effect 自身的狀態(tài)
  effect.active = true
  // 包裝的原始函數(shù)
  effect.raw = fn
  // effect 對應(yīng)的依賴,雙向指針,依賴包含對 effect 的引用,effect 也包含對依賴的引用
  effect.deps = []
  // effect 的相關(guān)配置
  effect.options = options
  return effect
}
  • effect 內(nèi)部通過執(zhí)行 createReactiveEffect 函數(shù)去創(chuàng)建一個新的 effect 函數(shù),為了和外部的 effect 函數(shù)區(qū)分,我們把它稱作 reactiveEffect 函數(shù),并且還給它添加了一些額外屬性
  • 這個 reactiveEffect 函數(shù)就是響應(yīng)式的副作用函數(shù),當執(zhí)行 trigger 過程派發(fā)通知的時候,執(zhí)行的 effect 就是它
  • reactiveEffect 函數(shù)只需要做兩件事情
  • 1)把全局的 activeEffect 指向它
  • 2)然后執(zhí)行被包裝的原始函數(shù) fn
  • effectStack維護一個棧,解決嵌套場景,activeEffect指向問題,activeEffect 指向 effectStack 最后一個元素
  • 入棧前會執(zhí)行 cleanup 函數(shù)清空 reactiveEffect函數(shù)對應(yīng)的依賴

readonly API

  • 創(chuàng)建只讀對象,不能修改它的屬性,也不能給這個對象添加和刪除屬性
function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers)
}
  • readonlyreactive 函數(shù)的主要區(qū)別,就是執(zhí)行 createReactiveObject 函數(shù)時的參數(shù) isReadonly 不同。
  • 首先 isReadonly 變量為 true,所以在創(chuàng)建過程中會給原始對象 target 打上一個 __v_readonly 的標識
  • 另外還有一個特殊情況,如果 target 已經(jīng)是一個 reactive 對象,就會把它繼續(xù)變成一個 readonly 響應(yīng)式對象
  • 創(chuàng)建代理是,傳入readonlyHandlers
const readonlyHandlers = {
  get: readonlyGet,
  has,
  ownKeys,
  set(target, key) {
    if ((process.env.NODE_ENV !== 'production')) {
      console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target)
    }
    return true
  },
  deleteProperty(target, key) {
    if ((process.env.NODE_ENV !== 'production')) {
      console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target)
    }
    return true
  }
}
  • readonlyHandlersmutableHandlers的區(qū)別主要在 get、setdeleteProperty 三個函數(shù)上
  • 在非生產(chǎn)環(huán)境下 setdeleteProperty 函數(shù)的實現(xiàn)都會報警告,提示用戶 targetreadonly
  • readonlyGet的實現(xiàn),即createGetter(true)
function createGetter(isReadonly = false) {
  return function get(target, key, receiver) {
    // ...
    // isReadonly 為 true 則不需要依賴收集
    !isReadonly && track(target, "get" /* GET */, key)
    return isObject(res)
      ? isReadonly
        ?
        // 如果 res 是個對象或者數(shù)組類型,則遞歸執(zhí)行 readonly 函數(shù)把 res readonly
        readonly(res)
        : reactive(res)
      : res
  }
}
  • reactive API 最大的區(qū)別就是不做依賴收集

ref API

  • reactive API對傳入的 target 類型有限制,必須是對象或者數(shù)組類型,而對于一些基礎(chǔ)類型(比如 String、Number、Boolean)是不支持的,因此有了ref API
  • 使用
const msg = ref('Hello World') 
msg.value = 'Hello Vue'
  • ref 的實現(xiàn)
function ref(value) {
  return createRef(value)
}
const convert = (val) => isObject(val) ? reactive(val) : val
function createRef(rawValue) {
  if (isRef(rawValue)) {
    // 如果傳入的就是一個 ref,那么返回自身即可,處理嵌套 ref 的情況。
    return rawValue
  }
  // 如果是對象或者數(shù)組類型,則轉(zhuǎn)換一個 reactive 對象。
  let value = convert(rawValue)
  const r = {
    __v_isRef: true,
    get value() {
      // getter
      // 依賴收集,key 為固定的 value
      track(r, "get" /* GET */, 'value')
      return value
    },
    set value(newVal) {
      // setter,只處理 value 屬性的修改
      if (hasChanged(toRaw(newVal), rawValue)) {
        // 判斷有變化后更新值
        rawValue = newVal
        value = convert(newVal)
        // 派發(fā)通知
        trigger(r, "set" /* SET */, 'value', void 0)
      }
    }
  }
  return r
}
  • 首先處理嵌套ref,如果傳入的 rawValue 也是 ref,那么直接返回
  • 然后對rawValue 做了一層轉(zhuǎn)換,如果 rawValue 是對象或者數(shù)組類型,那么把它轉(zhuǎn)換成一個 reactive 對象。
  • 最后定義一個對 value 屬性做 gettersetter 劫持的對象并返回
    • get 部分就是執(zhí)行 track 函數(shù)做依賴收集然后返回它的值
    • set 部分就是設(shè)置新值并且執(zhí)行 trigger 函數(shù)派發(fā)通知
      image.png
  • 區(qū)別于vue2.x
  • 1)劫持數(shù)據(jù)的方式改成用 Proxy 實現(xiàn)
  • 2)收集的依賴由 watcher 實例變成了組件副作用渲染函數(shù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容