vue3響應(yīng)式原理之Ref用法及說明
theme: fancy
一. Ref 用法
這是 ref 最基本的用法,返回來的count是一個響應(yīng)式的代理值
const count = ref(0)
二. 實現(xiàn)
1. ref 函數(shù)
我們調(diào)用的ref函數(shù),傳進(jìn)來一個 val 值,調(diào)用 createRef 函數(shù),我們來看下該函數(shù)的實現(xiàn)
源碼路徑:packages/reactivity/src/ref.ts
function ref(value?: unknown) {
? return createRef(value, false)
}2. createRef 函數(shù)
該函數(shù)里邊做了一個判斷,該值如果已經(jīng)是一個Ref,則直接返回,否則創(chuàng)建一個 Ref 實例,讓我們看下 RefImpl 類的實現(xiàn)
源碼路徑:packages/reactivity/src/ref.ts
function createRef(rawValue: unknown, shallow: boolean) {
? if (isRef(rawValue)) {
? ? return rawValue
? }
? return new RefImpl(rawValue, shallow)
}3. RefImpl 類的實現(xiàn)
定義了四個變量
_value: 用來保存加工后實現(xiàn)響應(yīng)化的值_rawValue: 用來保存當(dāng)前未經(jīng)加工過的值dep: 用來收集依賴,是一個 Set類型_v_isRef: 用來標(biāo)識該值是否經(jīng)過 ref 加工
在constructor中,我們?yōu)開rawValue 和_value實現(xiàn)了初始化,分別調(diào)用了toRaw和toReactive函數(shù),toReactive將 object類型轉(zhuǎn)換為響應(yīng)式,所以ref中也可以實現(xiàn)對象的響應(yīng)式管理,我們將他放在下篇文章中來講,它是實現(xiàn)對象響應(yīng)式的主要方法,我們今天只講基本類型。
源碼路徑:packages/reactivity/src/ref.ts
class RefImpl<T> {
? private _value: T
? private _rawValue: T
? public dep?: Dep = undefined
? public readonly __v_isRef = true
? constructor(value: T, public readonly __v_isShallow: boolean) {
? ? this._rawValue = __v_isShallow ? value : toRaw(value)
? ? this._value = __v_isShallow ? value : toReactive(value)
? }
? get value() {
? ? trackRefValue(this)
? ? return this._value
? }
? set value(newVal) {
? ? newVal = this.__v_isShallow ? newVal : toRaw(newVal)
? ? if (hasChanged(newVal, this._rawValue)) {
? ? ? this._rawValue = newVal
? ? ? this._value = this.__v_isShallow ? newVal : toReactive(newVal)
? ? ? triggerRefValue(this, newVal)
? ? }
? }
}4. trackRefValue 依賴收集
從上邊代碼中我們可以看到,我們使用 get 和 set對 value 屬性實現(xiàn)了攔截,其實如同Object.defineProperty,在 get中實現(xiàn)依賴收集,set中通知依賴更新,而 vue3中是通過調(diào)用trackRefValue來實現(xiàn)跟蹤依賴。
在該方法中,調(diào)用了trackEffect方法,在收集第一個依賴時執(zhí)行createDep方法來作為參數(shù)傳入。
我們接著往下看。
源碼路徑:packages/reactivity/src/ref.ts
function trackRefValue(ref: RefBase<any>) {
? if (shouldTrack && activeEffect) {
? ? ref = toRaw(ref)
? ? if (__DEV__) {
? ? ? trackEffects(ref.dep || (ref.dep = createDep()), {
? ? ? ? target: ref,
? ? ? ? type: TrackOpTypes.GET,
? ? ? ? key: 'value'
? ? ? })
? ? } else {
? ? ? trackEffects(ref.dep || (ref.dep = createDep()))
? ? }
? }
}5. createDep 創(chuàng)建依賴容器
其實就是創(chuàng)建了一個 Set 類型,之后我們進(jìn)入到下一步 trackEffects。
源碼路徑:packages/reactivity/src/dep.ts
export const createDep = (effects?: ReactiveEffect[]): Dep => {
? const dep = new Set<ReactiveEffect>(effects) as Dep
? dep.w = 0
? dep.n = 0
? return dep
}6. trackEffects
可以看到這里我們將activeEffect 放入的我們的Ref的dep中,也就是Set類型,那這個activeEffect是什么呢?
它其實就是我們的componentUpdateFn,每次更改value值時,通知對應(yīng)的組件更新。我們來看下activeEffect是如何賦值的。
源碼路徑:packages/reactivity/src/effect.ts
export function trackEffects(
? dep: Dep,
? debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
? if (shouldTrack) {
? ? // 放入我們的依賴集合?
? ? dep.add(activeEffect!)
? ? activeEffect!.deps.push(dep)
}三. activeEffect 的賦值
我們在上邊說過,activeEffect其實就是componentUpdateFn函數(shù),所以該值應(yīng)該是一個變化的值,它是如何準(zhǔn)確無誤的將每個組件更新函數(shù)來放入到對應(yīng)的dep中的呢,我們回到setupRenderEffect函數(shù)里邊來看一下。
//源碼路徑 core/packages/runtime-core/src/renderer.ts
? const setupRenderEffect: SetupRenderEffectFn = (
? ? instance,
? ? initialVNode,
? ? container,
? ? anchor,
? ? parentSuspense,
? ? isSVG,
? ? optimized
? ) => {
? ? const componentUpdateFn = () => { }
? ? // create reactive effect for rendering
? ? const effect = (instance.effect = new ReactiveEffect(
? ? ? componentUpdateFn,
? ? ? () => queueJob(instance.update),
? ? ? instance.scope // track it in component's effect scope
? ? ))
? ? const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
? ? update.id = instance.uid
? ? // allowRecurse
? ? // #1801, #2043 component render effects should allow recursive updates
? ? toggleRecurse(instance, true)
? ? update()
? }從上邊源碼中我們可以看到,在setRenderEffectFn方法中實現(xiàn)了componentUpdateFn的定義,同時在此時創(chuàng)建了一個ReactiveEfect的對象,同時調(diào)用了ReactiveEffect中的run方法,并且使用bind來改變其中的this指向該effect。我們來進(jìn)一步看下run中發(fā)生了什么。
源碼路徑:packages/reactivity/src/effect.ts
? run() {
? ? if (!this.active) {
? ? ? return this.fn()
? ? }
? ? let parent: ReactiveEffect | undefined = activeEffect
??
? ? try {
? ? ? this.parent = activeEffect
? ? ? activeEffect = this
? ? ? shouldTrack = true
? ? ? trackOpBit = 1 << ++effectTrackDepth
? ? ? if (effectTrackDepth <= maxMarkerBits) {
? ? ? ? initDepMarkers(this)
? ? ? } else {
? ? ? ? cleanupEffect(this)
? ? ? }
? ? ? return this.fn()
? ? } finally {
? ? ? if (effectTrackDepth <= maxMarkerBits) {
? ? ? ? finalizeDepMarkers(this)
? ? ? }
? ? ? trackOpBit = 1 << --effectTrackDepth
? ? ? activeEffect = this.parent
? ? ? shouldTrack = lastShouldTrack
? ? ? this.parent = undefined
? ? }
? }從上邊代碼中我們看到,在try中,我們先將this賦值給activeEffect,然后調(diào)用傳入的componentUpdateFn函數(shù),在該函數(shù)中會獲取我們定義的ref變量的值,觸發(fā)get,然后將該this也就是effect保存到該Ref的dep中,這就完成了我們的整個依賴收集的過程。
四. 依賴收集
收集依賴后,下一步就是在數(shù)據(jù)改變之后通知依賴更新,我們來看下set中是如何做的。
在set中我們調(diào)用了triggerRefValue方法,傳入了this,也就是當(dāng)前Ref實例,還有新的值。
源碼路徑:packages/reactivity/src/ref.ts
? set value(newVal) {
? ? newVal = this.__v_isShallow ? newVal : toRaw(newVal)
? ? if (hasChanged(newVal, this._rawValue)) {
? ? ? this._rawValue = newVal
? ? ? this._value = this.__v_isShallow ? newVal : toReactive(newVal)
? ? ? triggerRefValue(this, newVal)
? ? }
? }1. triggerRefValue
該方法中調(diào)用了triggerEffects方法,將ref.dep也就是之前收集依賴的Set,傳入。讓我們接著往下看。
// 源碼路徑:packages/reactivity/src/ref.ts
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
? ref = toRaw(ref)
? if (ref.dep) {
? ? if (__DEV__) {
? ? ? triggerEffects(ref.dep, {
? ? ? ? target: ref,
? ? ? ? type: TriggerOpTypes.SET,
? ? ? ? key: 'value',
? ? ? ? newValue: newVal
? ? ? })
? ? } else {
? ? ? triggerEffects(ref.dep)
? ? }
? }
}2. triggerEffects
該方法中,遍歷Set,然后調(diào)用每個依賴上的run方法,也就是執(zhí)行更新函數(shù),進(jìn)而使頁面刷新。實現(xiàn)數(shù)據(jù)到頁面的響應(yīng)式渲染。
// 源碼路徑:packages/reactivity/src/effect.ts
export function triggerEffects(
? dep: Dep | ReactiveEffect[],
? debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
? // spread into array for stabilization
? for (const effect of isArray(dep) ? dep : [...dep]) {
? ? if (effect !== activeEffect || effect.allowRecurse) {
? ? ? if (__DEV__ && effect.onTrigger) {
? ? ? ? effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
? ? ? }
? ? ? if (effect.scheduler) {
? ? ? ? effect.scheduler()
? ? ? } else {
? ? ? ? effect.run()
? ? ? }
? ? }
? }
}五. 總結(jié)
vue3 中對Ref的實現(xiàn)基本沒有太大改變,也是利用setter和getter對數(shù)據(jù)實現(xiàn)數(shù)據(jù)劫持,而Reactive的響應(yīng)式原理就與vue2的方案截然不同了。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于vue-cli-service:command?not?found報錯引發(fā)的實戰(zhàn)案例
這篇文章主要給大家介紹了關(guān)于vue-cli-service:command?not?found報錯引發(fā)的相關(guān)資料,文中通過實例代碼將解決的過程介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-02-02
vue?perfect-scrollbar(特定框架里使用非該框架定制庫/插件)
這篇文章主要為大家介紹了vue?perfect-scrollbar在特定框架里使用一款并非為該框架定制的庫/插件如何實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-05-05
VUE+node(express)實現(xiàn)前后端分離
在本篇文章里小編給大家分享的是關(guān)于VUE+node(express)前后端分離實例內(nèi)容,有需要的朋友們參考下。2019-10-10

