Vue 3.0 響應式方法解析

前一段時間,尤雨溪公佈了Vue 3.0 的核心部分代碼。咱們趁着目前代碼量還比較少,來趕忙學一下。vue

Vue3.0 倉庫react

目錄結構

首先看一下 Vue3.0 的文件目錄結構,全部的核心代碼都在 packages 文件夾下:git

reactivity :Vue3.0的數據響應式系統,咱們確定都據說 Vue3 摒棄了 Object.defineProperty,基於 Proxy 實現了一套響應式系統,就是這個文件夾下。github

runtime-coreruntime-domruntime-test,這三個文件夾都是Vue 運行時相關的核心代碼。bash

compiler-corecompiler-domcompiler-sfc,這三個文件夾是 Vue 實現的編譯器相關代碼。數據結構

server-renderer 是服務端渲染部分。dom

vue 文件夾是整個項目打包構建以後的出口文件。函數

reactivity

咱們首先來閱讀 reactivity 響應式系統相關的代碼。 響應式系統分別有幾個文件:: ref 、reactive 、baseHandlers、collectionHandlers、computed 、effect 、lock 、operations。工具

其中lock其中有兩個控制鎖的開關的方法,operations裏分別枚舉了幾種數據操做類型。ui

reactive: 響應式系統的核心部分,Proxy 就在這裏了。

ref:ref 其實提供了一套 Ref 類型,它的主要做用是讓JS的基礎數據類型也能成爲響應式數據被監測。

computed: computed 不用多說,就是 Vue2 中咱們已經熟悉的計算屬性了。

baseHandlerscollectionHandlers,收集依賴和觸發監聽方法的 Handlers

effect:effect 裏面包含了具體如何收集依賴和觸發監聽方法的處理邏輯。

reactive

今天主要分析的就是 reactive 文件,話很少說,咱們直接上代碼:

首先文件的一開始分別引入了一下外部代碼:

import { isObject, toRawType } from '@vue/shared'
複製代碼

isObject 判斷數據是不是對象,toRawType 獲取數據類型。都是工具方法。

import {
  mutableHandlers, // 可變數據的 Handlers
  readonlyHandlers, // 只讀數據的 Handlers
  readonlyPropsHandlers  // 只讀 Props 的 Handlers
} from './baseHandlers'

import {
  mutableCollectionHandlers, // 可變集合數據的 Handlers
  readonlyCollectionHandlers // 只讀集合數據的 Handlers
} from './collectionHandlers'
複製代碼

上面引入了針對三種數據的 handlers 處理。

import { ReactiveEffect } from './effect'
複製代碼

從 effect 文件引入了一種數據類型。

import { UnwrapRef, Ref } from './ref'
複製代碼

從 ref 文件引入了兩種數據類型。

import { makeMap } from '@vue/shared'
複製代碼

生成 map 的工具方法。

常量

export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<any, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()
複製代碼

定義了一個 targetMap 常量,爲了減小內存開銷,使用 WeakMap 數據類型,可是還不知道這個 targetMap 是用來作什麼的。

const rawToReactive = new WeakMap<any, any>() // 原始數據 和 響應式數據的映射
const reactiveToRaw = new WeakMap<any, any>() // 響應式數據 和 原始數據的映射
const rawToReadonly = new WeakMap<any, any>() // 原始數據和只讀的映射
const readonlyToRaw = new WeakMap<any, any>() // 只讀數據和原始數據的映射
複製代碼

這裏定義了四個 WeakMap 的常量來進行數據的雙向映射。

const readonlyValues = new WeakSet<any>() // 被用戶標記爲只讀類型的數據
const nonReactiveValues = new WeakSet<any>() // 被用戶標記爲不能轉換成響應式的的數據
// 幾種集合數據類型
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
// 是不是可監測類型數據
const isObservableType = /*#__PURE__*/ makeMap(
  'Object,Array,Map,Set,WeakMap,WeakSet'
)
複製代碼

核心方法

除了上面定義的一些常量,還有一些工具方法,基本很簡單易懂,咱們直接來看核心方法。 這裏有三個方法,分別是 reactivereadonlyreadonlyProps 方法:

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.
  // 已經在只讀map裏,說明已是響應式數據,直接 return
  if (readonlyToRaw.has(target)) {
    return target
  }
  // target is explicitly marked as readonly by user
  // 被用戶標記爲只讀類型,調用只讀方法
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  // 調用 建立響應式對象方法
  return createReactiveObject(
    target, // 目標對象
    rawToReactive, // 原始數據 map
    reactiveToRaw, // 響應式數據 map
    mutableHandlers, // 可變數據的處理
    mutableCollectionHandlers // 可變集合的處理
  )
}

export function readonly<T extends object>(
  target: T
): Readonly<UnwrapNestedRefs<T>> {
  // value is a mutable observable, retrieve its original and return
  // a readonly version.
  // 若是響應式 map 裏有目標對象,從 map 裏取出
  if (reactiveToRaw.has(target)) {
    target = reactiveToRaw.get(target)
  }
  // 調用 建立響應式對象方法
  return createReactiveObject(
    target,
    rawToReadonly,
    readonlyToRaw,
    readonlyHandlers,
    readonlyCollectionHandlers
  )
}

export function readonlyProps<T extends object>(
  target: T
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
  return createReactiveObject(
    target,
    rawToReadonly,
    readonlyToRaw,
    readonlyPropsHandlers,
    readonlyCollectionHandlers
  )
}
複製代碼

從以上能夠看出,除了一些相應校驗以外,主要調用了 createReactiveObject 方法。

function createReactiveObject(
  target: unknown,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 判斷 target 是不是對象
  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
  // 原始數據 已經在 toRaw 裏,說明已是響應式數據
  // 直接返回 target
  if (toRaw.has(target)) {
    return target
  }
  // only a whitelist of value types can be observed.
  // 目標數據不能被 監聽
  if (!canObserve(target)) {
    return target
  }
  // 根據數據是不是集合找尋對應的 handlers
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers

  // 使用 new Proxy 監聽,返回一個響應式數據
  observed = new Proxy(target, handlers)
  // 設置原始數據與響應式數據的雙向映射
  toProxy.set(target, observed)
  toRaw.set(observed, target) 
  // 若是 targetMap 裏沒有 入參的原始數據,那麼 set 進 targetMap 裏
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map())
  }
  return observed
}
複製代碼

咱們能夠看到 createReactiveObject 方法來生成了一個響應式數據,可是具體的收集依賴和監聽函數都在 handlers 裏。但 handlers 裏是怎麼個操做邏輯呢,這個具體等以後的文章來分析。

另外 createReactiveObject 裏用到了上面咱們不知因此的 targetMap 常量,那麼再回頭來看一下。

export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<any, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()
複製代碼

targetMap 的 key 就是咱們在 createReactiveObject 方法裏傳入的 target,而 value 是一個 KeyToDepMap 類型的數據,是一個 any 類型的值 和 Dep 的映射。 Dep 又是一個 ReactiveEffect 類型的 Set 集合。

能夠看出來 targetMap 是一個三維的數據結構。 後續若是看到 effect 部分代碼的話,咱們會知道 KeyToDepMap 的 key,實際上是 target 對象的各個屬性,而 Dep 固然就是存對應的監聽函數了。

總結

其實這部分的代碼比較簡單,主要是幾個數據映射和一些校驗判斷,更復雜的邏輯仍是在 handlerseffect。Vue3.0 徹底是使用 TS 編寫,我也是初學 TS,因此讀起來仍是有些吃力的,因此先解讀這一部分的代碼,後續再繼續深刻。

相關文章
相關標籤/搜索