vue3響應式原理

Vue3源碼終於發佈了!火燒眉毛的擼一下源碼,看看Vue3和Vue2到底有什麼區別。html

以前寫過兩篇Vue2響應式原理的文章:vue

從源碼解析vue的響應式原理-依賴收集、依賴觸發react

從源碼解析vue的響應式原理-響應式的總體流程git

Vue2的原理是:github

  • 對object類型使用defineProperty重寫對象的getter和setter函數,在getter中收集依賴,在setter中觸發依賴,以此實現響應式。可是要遞歸觀測object中的全部key,會有性能問題。
  • 對array類型的數據,攔截修改數組的幾個方法:push、pop、shift、unshift、splice、sort、reverse以此實現響應式。可是當數組中的元素爲基本類型的數據時沒法被觀測。

Vue3的響應式原理是用了proxy的方式來實現,優化了Vue2響應式存在的幾個問題,今天就從源碼來分析下vue3的響應式原理:typescript

(Vue3的源碼是使用ts開發的,須要你們提早學習ts相關知識)api

先從使用聊起

在Vue3中咱們想要建立一個響應式數據,要怎麼作呢?數組

查看下官方api咱們看到一段最基礎的示例代碼:app

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })

    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
</script>
複製代碼

示例中咱們發現使用reactive生成的state是響應對象,當state.count變化時,依賴state.count的template片斷和double都會相應的變化。函數

那麼reactive到底作了什麼使數據變成了響應對象呢?

####解析reactive

reactive和Vue2中的Vue.observable()相似,返回一個響應對象;reactive返回的響應對象主要用於頁面顯示,當響應對象改變時視圖會自動從新渲染,實現數據和視圖的雙向綁定。

此處解析代碼:

reactive

代碼結構很是清晰,咱們能夠很明白的看到reactive函數的入參必須是一個Object類型的數據,而返回則是一個UnwrapNestedRefs類型的對象(被解嵌套之後的響應式對象,後面會分析UnwrapNestedRefs),此處能夠直接簡單的理解爲返回了一個響應式對象。

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (readonlyToRaw.has(target)) {
    return target
  }
  // target is explicitly marked as readonly by user
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    // 弱引用的map結構,用於保存原始數據對應的響應式數據
    rawToReactive,
    // 弱引用的map結構,用於保存響應式數據對應的原始數據
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}
複製代碼
createReactiveObject

reactive中關鍵點在於調用createReactiveObject方法,經過該方法返回傳入對象的對應的響應式對象。在createReactiveObject方法中調用new proxy,生成一個代理對象,將該代理對象做爲最終的響應式對象並返回。

function createReactiveObject( target: any, // 保存原始數據的weakMap toProxy: WeakMap<any, any>, // 保存響應式數據的weakMap toRaw: WeakMap<any, any>, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
  // reactive的數據只能是Object類型
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target already has corresponding Proxy
  // 數據已經被轉化爲響應式數據了,直接返回其對應的響應式對象
  let observed = toProxy.get(target)
  if (observed !== void 0) {
    return observed
  }
  // target is already a Proxy
  // 數據自己就是一個響應式對象,則直接返回數據自己
  if (toRaw.has(target)) {
    return target
  }
  // only a whitelist of value types can be observed.
  // 一些不可被觀測的對象,直接返回
  if (!canObserve(target)) {
    return target
  }
  // 定義new proxy中的處理函數handlers
  // 集合類型的對象使用 collectionHandlers.ts中的 mutableCollectionHandlers
  // 其餘類型的對象使用 baseHandlers.ts中的 mutableHandlers
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
  // 調用new proxy,生成一個代理對象,將該代理對象做爲最終的響應式對象並返回
  observed = new Proxy(target, handlers)
  toProxy.set(target, observed)
  toRaw.set(observed, target)
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map())
  }
  return observed
}
複製代碼

其中調用new Proxy傳入的handler是響應式的關鍵,集合類型的對象使用 collectionHandlers.ts中的 mutableCollectionHandlers做爲new Proxy的handler;其餘類型的對象使用 baseHandlers.ts中的 mutableHandlers做爲new Proxy的handler。

mutableHandlers

baseHandlers.ts中的 mutableHandlers中使用createGetter代理對象的get方法、set代理對象的set方法。

其中createGetter方法中作了四件事:一、獲取數據的值;二、判斷數據是否已經進行過響應式處理;三、使用track方法進行依賴收集;四、對數據的每個Object類型的屬性進行reactive遞歸

function createGetter(isReadonly: boolean) {
  return function get(target: any, key: string | symbol, receiver: any) {
    // 獲取數據的值
    const res = Reflect.get(target, key, receiver)
    // 已經通過響應式處理的ref數據則直接返回
    if (typeof key === 'symbol' && builtInSymbols.has(key)) {
      return res
    }
    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
  }
}
複製代碼

set方法中作了三件事:一、將set行爲更新到原屬數據對象上;二、判斷代理數據中有沒有相應的key,沒有則作新增處理,有則作修改處理;三、調用trigger方法,觸發其依賴;

function set( target: any, key: string | symbol, value: any, receiver: any ): boolean {
  value = toRaw(value)
  // 判斷代理對象中是否有這個key。如有的話就進行修改,若沒有則新增
  const hadKey = hasOwn(target, key)
  const oldValue = target[key]
  // 原值是ref類型,新值不是,則直接賦值,由於原值已經被監聽了set觸發trigger。此處避免重複觸發。
  if (isRef(oldValue) && !isRef(value)) {
    oldValue.value = value
    return true
  }
  // 將set行爲更新到原屬數據對象上
  const result = Reflect.set(target, key, value, receiver)
  // don't trigger if target is something up in the prototype chain of original
  // 若是target是該代理數據相對應的原始數據才作處理,若是targer只是代理數據相對應的原始數據原型鏈上的數據則不作操做
  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) {
        // 代理數據中沒有響應的key,則作新增處理,並觸發其依賴
        trigger(target, OperationTypes.ADD, key)
      } else if (value !== oldValue) {
        // 代理數據中有響應的key,則作修改處理,並觸發其依賴
        trigger(target, OperationTypes.SET, key)
      }
    }
  }
  return result
}
複製代碼
mutableCollectionHandlers

不作過多解釋,你們直接看這裏吧: vue3響應式源碼解析-Reactive篇-collectionHandlers

解析ref

從上述解析中咱們一直看到一個概念就是ref,那麼在vue3中ref究竟是什麼呢?vue3中主要的是ref()函數,ref()函數接受一個基本類型的數據,並返回一個響應式的ref對象。瞭解ref()函數以前須要瞭解下面兩個基本概念:Ref接口、UnwrapNestedRefs。

Ref接口

對Ref接口的定義如 ref.ts:

// Ref接口
export interface Ref<T> {
	// 惟一標識位,標識對象是一個ref對象
  [refSymbol]: true
  // 存放數據,是基本類型數據真正存在的地方,UnwrapNestedRefs表示被接嵌套之後的ref類型的對象
  value: UnwrapNestedRefs<T>
}
複製代碼
UnwrapNestedRefs

以前分析reactive時又講到過reactive返回的是一個UnwrapNestedRefs類型的數據,且上面說Ref接口中的value也是UnwrapNestedRefs類型的數據。

export type UnwrapNestedRefs<T> = T extends Ref<any> ? T : UnwrapRef<T>
複製代碼

由上面代碼可見UnwrapNestedRefs是Ref類型的數據,或者通過UnwrapRef接嵌套之後的數據。 UnwrapRef的定義以下:

// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = {
  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
  array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
  object: { [K in keyof T]: UnwrapRef<T[K]> }
  stop: T
}[T extends Ref<any>
  ? 'ref'
  : T extends Array<any>
    ? 'array'
    : T extends BailTypes
      ? 'stop' // bail out on types that shouldn't be unwrapped
      : T extends object ? 'object' : 'stop']

複製代碼

這個接嵌套寫的很是巧妙,其中經過infer V推斷出類型進行進一步的遞歸解構(其中infer語句表示在 extends 條件語句中待推斷的類型變量:infer定義),也就是說UnwrapNestedRefs只能是ref類型或者其餘任意類型的的對象,可是不能是嵌套了ref類型的對象,即不能是這樣Ref<Ref> 這樣Array<Ref> 或者這樣 { [key]: Ref }嵌套型的ref類型對象。

ref() 函數
// 判斷數據是否是對象,是對象的話調用reactive()函數將其變爲響應式數據,不是對象的話直接返回
const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
// ref函數,接受一個原始數據,返回其響應式的Ref類型的對象
export function ref<T>(raw: T): Ref<T> {
  raw = convert(raw)
  const v = {
    // 添加惟一標識位,標識對象是一個ref對象
    [refSymbol]: true,
    get value() {
      // 依賴收集
      track(v, OperationTypes.GET, '')
      // 返回get結果
      return raw
    },
    set value(newVal) {
      // 對新值進行響應式處理
      raw = convert(newVal)
      // 依賴觸發
      trigger(v, OperationTypes.SET, '')
    }
  }
  return v as Ref<T>
}
複製代碼

此處ref()主要解決基本類型的數據沒法變成響應式數據的問題。 ref和reactive的功能類似,ref用於將基本類型的數據轉爲響應式數據;reactive用於將對象類型的數據轉爲響應式數據

相關文章
相關標籤/搜索