?????? 前言:由于篇幅有點(diǎn)長窑邦,所以分成了兩節(jié)來分別介紹壕探,上節(jié)我們構(gòu)建了簡易版的 reactive,并通過分析實(shí)踐了解到 reactive 只能用于復(fù)雜數(shù)據(jù)類型李请,下面我們來分析一下 ref,剛剛開工导盅,各位打工人進(jìn)入狀態(tài)了嗎??????,廢話不多說乍炉,開整?????????
一嘁字、構(gòu)建基礎(chǔ)列表
大致目錄結(jié)構(gòu)
---| packages
---|---| reactivity // 響應(yīng)性模塊
---|---|---| src
---|---|---|---| index.ts 出口文件
---|---|---|---| ref.ts
---|---|---|---| reactive.ts
---|---|---|---| effect.ts
---|---|---|---| dep.ts
---|---|---|---| baseHandlers.ts
---|---| shared // 公共方法模塊
---|---|---| src
---|---|---|---| index.ts 出口文件
---|---|---|---| shapeFlags.ts
---|---| vue // 打包、測試實(shí)例衷恭、項(xiàng)目整體入口模塊
---|---|---| dist
---|---|---| examples
---|---|---| src
---|---|---|---| index.ts 出口文件
二纯续、開整
ref 目標(biāo):構(gòu)建 ref 函數(shù),分析為什么用 .value 去訪問數(shù)據(jù)窗看,ref 是如何分別處理復(fù)雜數(shù)據(jù)類型和簡單數(shù)據(jù)類型的倦炒?
1. 創(chuàng)建 packages/reactivity/src/ref.ts
模塊
import { createDep, Dep } from './dep'
import { activeEffect, trackEffects, triggerEffects } from './effect'
import { toReactive } from './reactive'
export interface Ref<T = any> { value: T }
/**
* ref 函數(shù)
* @param value unknown
*/
export function ref(value?: unknown) {
return createRef(value, false)
}
/**
* 創(chuàng)建 RefImpl 實(shí)例
* @param rawValue 原始數(shù)據(jù)
* @param shallow boolean 形數(shù)據(jù)逢唤,表示《淺層的響應(yīng)性(即:只有 .value 是響應(yīng)性的)》
*/
function createRef(rawValue: unknown, shallow: boolean) {
if(isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
// 私有屬性
private _value: T
private _rawValue: T
// 共有屬性
public dep?: Dep = undefined
// 標(biāo)記是否為 ref 類型
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// https://cn.vuejs.org/api/reactivity-advanced.html#shallowref
// 如果:__v_isShallow:true,則:value 不會被轉(zhuǎn)化為 reactive 數(shù)據(jù)魔慷。
// 如果:當(dāng)前的 value 是復(fù)雜數(shù)據(jù)類型的話著恩,則:會失去響應(yīng)性
this._value = __v_isShallow ? true : toReactive(value)
// 原始數(shù)據(jù)
this._rawValue = value
}
/**
* get:將對象屬性綁定到查詢該屬性時(shí)調(diào)用的函數(shù)
* ??:xxx.value 觸發(fā)該函數(shù)
*/
get value() {
trackRefValue(this)
return this._value
}
/**
* set:更新屬性值
* newValue:新數(shù)據(jù)
* this._rawValue: 原始數(shù)據(jù)(老數(shù)據(jù))
* hasChanged:對比數(shù)據(jù)是否發(fā)生了變化
*/
set value(newValue) {
// 更新數(shù)據(jù)
this._rawValue = newValue
// 更新 .value 的值
this._value = toReactive(newValue)
// 觸發(fā)依賴
triggerRefValue(this)
}
}
/**
* 為 ref 的 value 進(jìn)行依賴收集工作
*/
export function trackRefValue(ref) {
if(activeEffect) {
// 收集所有依賴
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
/**
* 為 ref 的 value 進(jìn)行依賴觸發(fā)工作
*/
export function triggerRefValue(ref) {
if(ref.dep) {
triggerEffects(ref.dep)
}
}
/**
* 指定數(shù)據(jù)是否為 RefImpl 類型
*/
export function isRef(r: any) r is Ref {
return !!(r && r.__v_isRef === true)
}
packages/reactivity/src/reactive.ts
/**
* 將指定數(shù)據(jù)變?yōu)?reactive 類型
*/
export const toReactive = < T extends unknown>(value: T): T =>
isObject(value) ? reactive(value as object) : value
packages/shared/src/index.ts
/**
* 判斷是否為一個(gè)對象
*/
export const isObject = (val: unknown) => val !== null && typeof val === 'object'
/**
* 對比兩個(gè)數(shù)據(jù)是否發(fā)生了改變
*/
export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)
2. packages/vue/examples/reactivity/ref.html
小試牛刀
<body>
<div id="app"></div>
<script>
const { ref, effect } = Vue
const obj = ref('張三')
effect(() => {
document.querySelector('#app').innerText = obj.value
})
setTimeout(() => {
obj.value = '李四'
}, 2000)
</script>
</body>
??? 至此,我們的ref函數(shù)構(gòu)建完成纵顾,本質(zhì)上是生成了一個(gè)
RefImpl
類型的實(shí)例對象隧熙,通過get
和set
標(biāo)記處理了value
函數(shù)幻林,并且 ref 是通過toReactive
對簡單數(shù)據(jù)類型和復(fù)雜數(shù)據(jù)類型做了區(qū)分處理。
- 簡單數(shù)據(jù)類型:觸發(fā)
set value
屬性調(diào)用來更新數(shù)據(jù)躏敢,xxx.value
其實(shí)是觸發(fā)get value
屬性調(diào)用整葡,所以我們需要通過.value
進(jìn)行觸發(fā)達(dá)到類似數(shù)據(jù)的響應(yīng)。 - 復(fù)雜數(shù)據(jù)類型:轉(zhuǎn)化為
reactive
返回的proxy
實(shí)例遭居。value.xxx = 'xxx'
實(shí)際觸發(fā)的是proxy
的setter
。