前一段時間,尤雨溪公佈了Vue 3.0 的核心部分代碼。咱們趁着目前代碼量還比較少,來趕忙學一下。vue
Vue3.0 倉庫react
首先看一下 Vue3.0 的文件目錄結構,全部的核心代碼都在 packages 文件夾下:git
reactivity :Vue3.0的數據響應式系統,咱們確定都據說 Vue3 摒棄了 Object.defineProperty,基於 Proxy 實現了一套響應式系統,就是這個文件夾下。github
runtime-core、runtime-dom、runtime-test,這三個文件夾都是Vue 運行時相關的核心代碼。bash
compiler-core、compiler-dom、compiler-sfc,這三個文件夾是 Vue 實現的編譯器相關代碼。數據結構
server-renderer 是服務端渲染部分。dom
vue 文件夾是整個項目打包構建以後的出口文件。函數
咱們首先來閱讀 reactivity 響應式系統相關的代碼。 響應式系統分別有幾個文件:: ref 、reactive 、baseHandlers、collectionHandlers、computed 、effect 、lock 、operations。工具
其中lock
其中有兩個控制鎖的開關的方法,operations
裏分別枚舉了幾種數據操做類型。ui
reactive
: 響應式系統的核心部分,Proxy 就在這裏了。
ref
:ref 其實提供了一套 Ref
類型,它的主要做用是讓JS的基礎數據類型也能成爲響應式數據被監測。
computed
: computed 不用多說,就是 Vue2 中咱們已經熟悉的計算屬性了。
baseHandlers
、collectionHandlers
,收集依賴和觸發監聽方法的 Handlers
effect
:effect 裏面包含了具體如何收集依賴和觸發監聽方法的處理邏輯。
今天主要分析的就是 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'
)
複製代碼
除了上面定義的一些常量,還有一些工具方法,基本很簡單易懂,咱們直接來看核心方法。 這裏有三個方法,分別是 reactive
、readonly
、readonlyProps
方法:
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
固然就是存對應的監聽函數了。
其實這部分的代碼比較簡單,主要是幾個數據映射和一些校驗判斷,更復雜的邏輯仍是在 handlers
和 effect
。Vue3.0 徹底是使用 TS 編寫,我也是初學 TS,因此讀起來仍是有些吃力的,因此先解讀這一部分的代碼,後續再繼續深刻。