Vue3.0 響應(yīng)式系統(tǒng)源碼逐行分析講解
前言
關(guān)于響應(yīng)式原理想必大家都很清楚了,下面我將會(huì)根據(jù)響應(yīng)式API來(lái)具體講解Vue3.0中的實(shí)現(xiàn)原理, 另外我只會(huì)針對(duì)get,set進(jìn)行深入分析,本文包含以下API實(shí)現(xiàn),推薦大家順序閱讀
- effect
- reactive
- readonly
- computed
- ref
對(duì)了,大家一定要先知道怎么用哦~
引子
先來(lái)段代碼,大家可以直接復(fù)制哦,注意引用的文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../packages/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { reactive, computed, effect, watch, createApp } = Vue
const App = {
template: `
<div id="box">
<button @click="increment">{{ state.count }}</button>
</div>
`,
setup() {
const state = reactive({
count: 0
})
function increment(e) {
state.count++
}
effect(() => {
console.log('count改變', state.count);
})
return {
state,
increment
}
}
}
createApp().mount(App, '#app')
</script>
</body>
</html>
這段代碼,想必大家都看得懂,點(diǎn)擊后count增加,視圖也隨之更新,effect監(jiān)聽(tīng)了count改變,那么為什么effect能觀察到count變化呢,還有為什么reactive可以實(shí)現(xiàn)響應(yīng)式?
effect
為什么要先說(shuō)這個(gè)函數(shù)呢,因?yàn)樗推渌瘮?shù)都息息相關(guān),只有先了解它才能更好的理解其他響應(yīng)式API
上源碼
export function effect(
fn: Function,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
if ((fn as ReactiveEffect).isEffect) {
fn = (fn as ReactiveEffect).raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
if判斷,判斷如果傳入的fn函數(shù),它已經(jīng)是effect了,也就是一個(gè)標(biāo)識(shí),直接獲取該函數(shù)上的raw屬性,這個(gè)屬性后面會(huì)講到
調(diào)用createReactiveEffect
如果options中有l(wèi)azy,就會(huì)立即調(diào)用effect,其實(shí)本質(zhì)上調(diào)用的還是傳入的fn函數(shù)
// 了解一下options有哪些
{
lazy?: boolean // 是否立即調(diào)用fn
computed?: boolean // 是否是computed
scheduler?: (run: Function) => void // 在調(diào)用fn之前執(zhí)行
onTrack?: (event: DebuggerEvent) => void // 在依賴收集完成之后調(diào)用
onTrigger?: (event: DebuggerEvent) => void // 在調(diào)用fn之前執(zhí)行,源碼上來(lái)看和scheduler調(diào)用時(shí)機(jī)一樣,只是傳入?yún)?shù)不同
onStop?: () => void // 清除依賴完成后調(diào)用
}
返回effect
createReactiveEffect
上面提到了createReactiveEffect函數(shù),我們來(lái)看看它的實(shí)現(xiàn)
function createReactiveEffect(
fn: Function,
options: ReactiveEffectOptions
): ReactiveEffect {
// 又包裝了一層函數(shù)
const effect = function effect(...args): any {
return run(effect as ReactiveEffect, fn, args)
} as ReactiveEffect
effect.isEffect = true // 標(biāo)識(shí)effect
effect.active = true // 如果active
effect.raw = fn // 傳入的回調(diào)
effect.scheduler = options.scheduler
effect.onTrack = options.onTrack
effect.onTrigger = options.onTrigger
effect.onStop = options.onStop
effect.computed = options.computed
effect.deps = [] // 用于收集依賴
return effect
}
注意,敲黑板,這里有個(gè)run函數(shù),很重要,因?yàn)樗4媪艘蕾?br />
function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
if (!effect.active) {
return fn(...args)
}
if (activeReactiveEffectStack.indexOf(effect) === -1) {
cleanup(effect)
try {
activeReactiveEffectStack.push(effect)
return fn(...args)
} finally {
activeReactiveEffectStack.pop()
}
}
}
他把依賴存儲(chǔ)在了一個(gè)全局的數(shù)組中activeReactiveEffectStack, 他以棧的形式存儲(chǔ),調(diào)用完依賴后,會(huì)彈出,大家要留意一下這里,后面會(huì)用到
怎么樣,是不是很簡(jiǎn)單~
reactive
export function reactive(target: object) {
// 如果target是已經(jīng)被readonly對(duì)象,那么直接返回對(duì)應(yīng)的proxy對(duì)象
if (readonlyToRaw.has(target)) {
return target
}
// 如果target是已經(jīng)被readonly對(duì)象,那么直接返回對(duì)應(yīng)的真實(shí)對(duì)象
if (readonlyValues.has(target)) {
return readonly(target)
}
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}
前兩個(gè)if是用來(lái)處理這種情況的
// 情況一
const state1 = readonly({ count: 0 })
const state2 = reactive(state1)
// 情況二
const obj = { count: 0 }
const state1 = readonly(obj)
const state2 = reactive(obj)
可以看到reactive它的參數(shù)是被readonly的對(duì)象,reactive不會(huì)對(duì)它再次創(chuàng)建響應(yīng)式,而是通過(guò)Map映射,拿到對(duì)應(yīng)的對(duì)象,即Proxy <==> Object的相互轉(zhuǎn)換。
createReactiveObject創(chuàng)建響應(yīng)式對(duì)象,注意它的參數(shù)
createReactiveObject(
target,
rawToReactive, // Object ==> Proxy
reactiveToRaw, // Proxy ==> Object
mutableHandlers, // get set has ...
mutableCollectionHandlers // 很少會(huì)用,不講了~
)
以上就是reative一開(kāi)始所做的一些事情,下面繼續(xù)分析createReactiveObject
createReactiveObject
function createReactiveObject(
target: any,
toProxy: WeakMap<any, any>,
toRaw: WeakMap<any, any>,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
// 如果不是對(duì)象,在開(kāi)發(fā)環(huán)境報(bào)出警告
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
let observed = toProxy.get(target)
// 如果目標(biāo)對(duì)象已經(jīng)有proxy對(duì)象,直接返回
if (observed !== void 0) {
return observed
}
// 如果目標(biāo)對(duì)象是proxy的對(duì)象,并且有對(duì)應(yīng)的真實(shí)對(duì)象,那么也直接返回
if (toRaw.has(target)) {
return target
}
// 如果它是vnode或者vue,則不能被觀測(cè)
if (!canObserve(target)) {
return target
}
// 判斷被觀測(cè)的對(duì)象是否是set,weakSet,map,weakMap,根據(jù)情況使用對(duì)應(yīng)proxy的,配置對(duì)象
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
}
第一個(gè)if,判斷是否是對(duì)象,否則報(bào)出警告
toProxy拿到觀測(cè)對(duì)象的Proxy對(duì)象,如果存在直接返回
// 這種情況
const obj = { count: 0 }
const state1 = reative(obj)
const state2 = reative(obj)
toRaw拿到Proxy對(duì)象對(duì)應(yīng)的真實(shí)對(duì)象,如果存在直接返回
// 這種情況
const obj = { count: 0 }
const state1 = reative(obj)
const state2 = reative(state1)
有些情況無(wú)法被觀測(cè),則直接返回觀測(cè)對(duì)象本身
const canObserve = (value: any): boolean => {
return (
!value._isVue &&
!value._isVNode &&
observableValueRE.test(toTypeString(value)) &&
!nonReactiveValues.has(value)
)
}
設(shè)置handlers,即get,set等屬性訪問(wèn)器, 注意:collectionHandlers是用來(lái)處理觀測(cè)對(duì)象為Set,Map等情況,很少見(jiàn),這里就不講了
const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
然后創(chuàng)建了Proxy對(duì)象,并把觀測(cè)對(duì)象和Proxy對(duì)象,分別做映射
observed = new Proxy(target, handlers) toProxy.set(target, observed) toRaw.set(observed, target)
然后在targetMap做了target ==> Map的映射,這又是干嘛,注意:targetMap是全局的
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
在這里先給大家賣個(gè)關(guān)子,targetMap非常重要,是用來(lái)保存依賴的地方
講完了reactive,可以回到一開(kāi)始的引子
依賴收集
說(shuō)到依賴收集,不得不提到,依賴的創(chuàng)建,那么Vue3.0是在哪里創(chuàng)建了渲染依賴呢,大家可以找到下面這段代碼以及文件
// vue-next\packages\runtime-core\src\createRenderer.ts
function setupRenderEffect(
instance: ComponentInternalInstance,
parentSuspense: HostSuspsenseBoundary | null,
initialVNode: HostVNode,
container: HostElement,
anchor: HostNode | null,
isSVG: boolean
) {
// create reactive effect for rendering
let mounted = false
instance.update = effect(function componentEffect() {
// ...
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
代碼特別長(zhǎng),我剪掉了中間部分,大家還記得effect有個(gè)選項(xiàng)lazy嗎,沒(méi)錯(cuò),它默認(rèn)是false,也就會(huì)立即調(diào)用傳入的componentEffect回調(diào),在它內(nèi)部調(diào)用了patch實(shí)現(xiàn)了組件的掛載。
敲黑板,關(guān)鍵來(lái)了,還記得effect調(diào)用,內(nèi)部會(huì)調(diào)用run方法嗎
function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
if (!effect.active) {
return fn(...args)
}
if (activeReactiveEffectStack.indexOf(effect) === -1) {
cleanup(effect)
try {
activeReactiveEffectStack.push(effect)
return fn(...args)
} finally {
activeReactiveEffectStack.pop()
}
}
}
這里進(jìn)行了第一步的依賴收集,保存在全局?jǐn)?shù)組中,為了方便觸發(fā)get的對(duì)象,將依賴收集到自己的deps中
然后就是調(diào)用patch,進(jìn)行組件掛載
if (!mounted) {
const subTree = (instance.subTree = renderComponentRoot(instance))
// beforeMount hook
if (instance.bm !== null) {
invokeHooks(instance.bm)
}
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
initialVNode.el = subTree.el
// mounted hook
if (instance.m !== null) {
queuePostRenderEffect(instance.m, parentSuspense)
}
mounted = true
}
至于它內(nèi)部實(shí)現(xiàn),我就不講了,不是本文重點(diǎn),然后我們?nèi)ゾ幾g的地方看看
//vue-next\packages\runtime-core\src\component.ts
function finishComponentSetup(
instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null
) {
const Component = instance.type as ComponentOptions
if (!instance.render) {
if (Component.template && !Component.render) {
if (compile) {
Component.render = compile(Component.template, {
onError(err) {}
})
} else if (__DEV__) {
warn(
`Component provides template but the build of Vue you are running ` +
`does not support on-the-fly template compilation. Either use the ` +
`full build or pre-compile the template using Vue CLI.`
)
}
}
if (__DEV__ && !Component.render) {
warn(
`Component is missing render function. Either provide a template or ` +
`return a render function from setup().`
)
}
instance.render = (Component.render || NOOP) as RenderFunction
}
// ...其他
}
上面的代碼是編譯部分,我們來(lái)看看例子中編譯后是什么樣
(function anonymous(
) {
const _Vue = Vue
const _createVNode = Vue.createVNode
const _hoisted_1 = { id: "box" }
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock("div", _hoisted_1, [
_createVNode("button", { onClick: increment }, _toString(state.count), 9 /* TEXT, PROPS */, ["onClick"])
]))
}
}
})
可以看到,編譯的代碼中,有使用到state.count,那么就會(huì)觸發(fā)get訪問(wèn)器,從而收集依賴,至于為什么能直接訪問(wèn)到屬性,原因是由于with設(shè)置了上下文,下面我們具體分析get
get
// vue-next\packages\reactivity\src\baseHandlers.ts
function createGetter(isReadonly: boolean) {
return function get(target: any, key: string | symbol, receiver: any) {
const res = Reflect.get(target, key, receiver)
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
return res
}
// _isRef
if (isRef(res)) {
return res.value
}
track(target, OperationTypes.GET, key)
// 如果該屬性對(duì)應(yīng)的值還是對(duì)象,就繼續(xù)遞歸創(chuàng)建響應(yīng)式
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
// circular dependency
readonly(res)
: reactive(res)
: res
}
}
調(diào)用Reflect.get獲取屬性值
如果key是symbol并且是Symbol的一個(gè)屬性,就直接返回該值
// 這種情況
const key = Symbol('key')
const state = reative({
[key]: 'symbol value'
})
state[key]
如果值為Ref返回該值的value,看到這里如果大家有了解過(guò)ref api的話就知道了,由于ref它自己實(shí)現(xiàn)了自己的get,set,所以不再需要執(zhí)行后面的邏輯,這個(gè)在后面會(huì)講
調(diào)用track
遞歸深度觀測(cè),使整個(gè)對(duì)象都為響應(yīng)式
下面我會(huì)詳細(xì)講解
track
在講它之前,先了解它有哪些參數(shù)
target: any, // 目標(biāo)對(duì)象
type: OperationTypes, // 追蹤數(shù)據(jù)變化類型,這里是get
key?: string | symbol // 需要獲取的key
export const enum OperationTypes {
SET = 'set',
ADD = 'add',
DELETE = 'delete',
CLEAR = 'clear',
GET = 'get',
HAS = 'has',
ITERATE = 'iterate'
}
export function track(
target: any,
type: OperationTypes,
key?: string | symbol
) {
if (!shouldTrack) {
return
}
// 獲取activeReactiveEffectStack中的依賴
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
if (effect) {
if (type === OperationTypes.ITERATE) {
key = ITERATE_KEY
}
// 獲取目標(biāo)對(duì)象對(duì)應(yīng)的依賴map
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
// 獲取對(duì)應(yīng)屬性的依賴
let dep = depsMap.get(key as string | symbol)
// 如果該依賴不存在
if (!dep) {
// 設(shè)置屬性對(duì)應(yīng)依賴
depsMap.set(key as string | symbol, (dep = new Set()))
}
// 如果屬性對(duì)應(yīng)依賴set中不存在該依賴
if (!dep.has(effect)) {
// 添加到依賴set中
dep.add(effect)
effect.deps.push(dep)
if (__DEV__ && effect.onTrack) {
// 調(diào)用onTrack鉤子
effect.onTrack({
effect,
target,
type,
key
})
}
}
}
}
activeReactiveEffectStack我兩次提到,從它這里拿到了依賴,注意后面執(zhí)行完依賴后,會(huì)從它里面彈出
如果effect存在
從targetMap中獲取對(duì)象,對(duì)飲的Map,具體的數(shù)據(jù)結(jié)構(gòu)類似這樣
const state = reative({
count: 0
})
effect(() => {
console.log(state.count)
})
// 依賴大致結(jié)構(gòu)(隨便寫(xiě)的,不太規(guī)范)
{
target(state):Map {
count: Set (componentEffect渲染依賴, user自己添加的依賴)
}
}
如果該對(duì)象不存在Map,就初始化一個(gè)
如果該Map中屬性對(duì)應(yīng)的Set不存在,就初始化一個(gè)Set
添加依賴到Set中
添加依賴到effect自身的deps數(shù)組中
最后調(diào)用onTrack回調(diào)
// 調(diào)用onTrack鉤子
effect.onTrack({
effect,
target,
type,
key
})
OK,Track實(shí)現(xiàn)大體就這樣,是不是也很簡(jiǎn)單,有了這些基礎(chǔ),后面要講的一些API就很容易理解了
set
當(dāng)我們點(diǎn)擊按鈕后,就會(huì)觸發(fā)set屬性訪問(wèn)器
function set(
target: any,
key: string | symbol,
value: any,
receiver: any
): boolean {
value = toRaw(value)
const hadKey = hasOwn(target, key)
const oldValue = target[key]
// 如果舊的值是ref,而新的值不是ref
if (isRef(oldValue) && !isRef(value)) {
// 直接更改原始ref即可
oldValue.value = value
return true
}
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
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) {
trigger(target, OperationTypes.ADD, key)
} else if (value !== oldValue) {
trigger(target, OperationTypes.SET, key)
}
}
}
return result
}
判斷舊值是ref,新值不是ref
// 這種情況
const val = ref(0)
const state = reative({
count: val
})
state.count = 1
// 其實(shí)state.count最終還是ref,還是能通過(guò)value訪問(wèn)
state.count.value // 1
調(diào)用Reflect.set修改值
開(kāi)發(fā)環(huán)境下,拿到新舊值組成的對(duì)象,調(diào)用trigger,為什么開(kāi)發(fā)環(huán)境要這么做呢,其實(shí)是為了方便onTrigger能拿到新舊值
trigger(target, OperationTypes.ADD, key, extraInfo)
可以看到第二個(gè)參數(shù)和track是一樣的enum,有兩種情況,一種我們?cè)O(shè)置了新的屬性和值,另一種修改了原有屬性值,下面我們來(lái)看看trigger實(shí)現(xiàn)。
trigger
export function trigger(
target: any,
type: OperationTypes,
key?: string | symbol,
extraInfo?: any
) {
const depsMap = targetMap.get(target)
if (depsMap === void 0) {
// never been tracked
return
}
// effect set
const effects: Set<ReactiveEffect> = new Set()
// computed effect set
const computedRunners: Set<ReactiveEffect> = new Set()
if (type === OperationTypes.CLEAR) {
depsMap.forEach(dep => {
addRunners(effects, computedRunners, dep)
})
} else {
// 添加effect到set中
if (key !== void 0) {
addRunners(effects, computedRunners, depsMap.get(key as string | symbol))
}
// also run for iteration key on ADD | DELETE
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
addRunners(effects, computedRunners, depsMap.get(iterationKey))
}
}
// 執(zhí)行set中的effect
const run = (effect: ReactiveEffect) => {
scheduleRun(effect, target, type, key, extraInfo)
}
computedRunners.forEach(run)
effects.forEach(run)
}
看到這個(gè)函數(shù)開(kāi)始的targetMap,大家應(yīng)該很清楚要干嘛了吧,沒(méi)錯(cuò),拿到對(duì)象的Map,它包含了屬性的所有依賴
- 如果沒(méi)有Map直接返回
- 創(chuàng)建了兩個(gè)Set,要干嘛用呢
// 用來(lái)保存將要執(zhí)行的依賴 const effects: Set<ReactiveEffect> = new Set() // computed依賴,因?yàn)閠rigger不僅是要處理effect,watch,還要處理computed惰性求值的情況 const computedRunners: Set<ReactiveEffect> = new Set()
處理三種情況CLEAR,ADD,DELETE,SET(這里沒(méi)有標(biāo)識(shí))
// effect set
const effects: Set<ReactiveEffect> = new Set()
// computed effect set
const computedRunners: Set<ReactiveEffect> = new Set()
function addRunners(
effects: Set<ReactiveEffect>,
computedRunners: Set<ReactiveEffect>,
effectsToAdd: Set<ReactiveEffect> | undefined
) {
if (effectsToAdd !== void 0) {
effectsToAdd.forEach(effect => {
if (effect.computed) {
computedRunners.add(effect)
} else {
effects.add(effect)
}
})
}
}
可以看到,三種情況實(shí)際上都差不多,唯一的區(qū)別就是,如果添加的對(duì)象是數(shù)組,就會(huì)拿到length屬性的依賴,用于修改數(shù)組長(zhǎng)度
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
addRunners(effects, computedRunners, depsMap.get(iterationKey))
}
執(zhí)行屬性對(duì)應(yīng)的依賴
// 執(zhí)行set中的effect
const run = (effect: ReactiveEffect) => {
scheduleRun(effect, target, type, key, extraInfo)
}
computedRunners.forEach(run)
effects.forEach(run)
function scheduleRun(
effect: ReactiveEffect,
target: any,
type: OperationTypes,
key: string | symbol | undefined,
extraInfo: any
) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(
extend(
{
effect,
target,
key,
type
},
extraInfo // { oldValue, newValue: value }
)
)
}
if (effect.scheduler !== void 0) {
effect.scheduler(effect)
} else {
effect()
}
}
最后調(diào)用了scheduleRun,它內(nèi)部會(huì)分別執(zhí)行onTrigger,scheduler,effect
需要注意的是,只有開(kāi)發(fā)環(huán)境才會(huì)執(zhí)行onTrigger,這也是為什么,前面要這么判斷
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)
}
}
readonly
有了前面的基礎(chǔ),readonly看起來(lái)會(huì)非常簡(jiǎn)單,唯一的區(qū)別就是rawToReadonly,rawToReadonly, readonlyHandlers
export function readonly(target: object) {
if (reactiveToRaw.has(target)) {
target = reactiveToRaw.get(target)
}
return createReactiveObject(
target,
rawToReadonly,
readonlyToRaw,
readonlyHandlers,
readonlyCollectionHandlers
)
}
前兩個(gè)大家應(yīng)該能猜出來(lái)了,關(guān)鍵是最后這個(gè)readonlyHandlers,區(qū)別就在set
set(target: any, key: string | symbol, value: any, receiver: any): boolean {
if (LOCKED) {
if (__DEV__) {
console.warn(
`Set operation on key "${key as any}" failed: target is readonly.`,
target
)
}
return true
} else {
return set(target, key, value, receiver)
}
}
它的實(shí)現(xiàn)很簡(jiǎn)單,不過(guò)LOCKED有是什么鬼,大家可以找到lock.ts
//vue-next\packages\reactivity\src\lock.ts
export let LOCKED = true
export function lock() {
LOCKED = true
}
export function unlock() {
LOCKED = false
}
看似簡(jiǎn)單,但是卻非常重要,它能夠控制被readonly的對(duì)象能夠暫時(shí)被更改,就比如我們常用的props,它是無(wú)法被修改的,但是Vue內(nèi)部又要對(duì)他進(jìn)行更新,那怎么辦,話不多說(shuō),我們?cè)僭创a中看他具體應(yīng)用
// vue-next\packages\runtime-core\src\componentProps.ts
export function resolveProps(
instance: ComponentInternalInstance,
rawProps: any,
_options: ComponentPropsOptions | void
) {
const hasDeclaredProps = _options != null
const options = normalizePropsOptions(_options) as NormalizedPropsOptions
if (!rawProps && !hasDeclaredProps) {
return
}
const props: any = {}
let attrs: any = void 0
const propsProxy = instance.propsProxy
const setProp = propsProxy
? (key: string, val: any) => {
props[key] = val
propsProxy[key] = val
}
: (key: string, val: any) => {
props[key] = val
}
unlock()
// 省略一些修改props操作。。
lock()
instance.props = __DEV__ ? readonly(props) : props
instance.attrs = options
? __DEV__ && attrs != null
? readonly(attrs)
: attrs
: instance.props
}
這里前后分別調(diào)用了unlock和lock,這樣就可以控制對(duì)readonly屬性的修改
那么readonly的講解就到這了
computed
export function computed<T>(
getterOrOptions: (() => T) | WritableComputedOptions<T>
): any {
const isReadonly = isFunction(getterOrOptions)
const getter = isReadonly
? (getterOrOptions as (() => T))
: (getterOrOptions as WritableComputedOptions<T>).get
const setter = isReadonly
? null
: (getterOrOptions as WritableComputedOptions<T>).set
let dirty: boolean = true
let value: any = undefined
const runner = effect(getter, {
lazy: true,
computed: true,
scheduler: () => {
dirty = true
}
})
return {
_isRef: true,
// expose effect so computed can be stopped
effect: runner,
get value() {
if (dirty) {
value = runner()
dirty = false
}
trackChildRun(runner)
return value
},
set value(newValue) {
if (setter) {
setter(newValue)
} else {
// TODO warn attempting to mutate readonly computed value
}
}
}
}
首先是前面這段
const isReadonly = isFunction(getterOrOptions) const getter = isReadonly ? (getterOrOptions as (() => T)) : (getterOrOptions as WritableComputedOptions<T>).get const setter = isReadonly ? null : (getterOrOptions as WritableComputedOptions<T>).set
大家都知道computed是可以單獨(dú)寫(xiě)一個(gè)函數(shù),或者get,set訪問(wèn)的,這里不多講
然后調(diào)用了effect,這里lazy設(shè)置為true, scheduler可以更改dirty為true
const runner = effect(getter, {
lazy: true,
computed: true,
scheduler: () => {
dirty = true
}
})
然后我們具體來(lái)看看,返回的對(duì)象
{
_isRef: true,
// expose effect so computed can be stopped
effect: runner,
get value() {
if (dirty) {
value = runner()
dirty = false
}
trackChildRun(runner)
return value
},
set value(newValue) {
if (setter) {
setter(newValue)
} else {
// TODO warn attempting to mutate readonly computed value
}
}
}
先說(shuō)說(shuō)set吧,尤大似乎還沒(méi)寫(xiě)完,只是單純能修改值
然后是get,注意dirty的變化,如果computed依賴了state中的值,初次渲染時(shí),他會(huì)調(diào)用依賴,然后dirty = false,關(guān)鍵來(lái)了,最后執(zhí)行了trackChildRun
function trackChildRun(childRunner: ReactiveEffect) {
const parentRunner =
activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
if (parentRunner) {
for (let i = 0; i < childRunner.deps.length; i++) {
const dep = childRunner.deps[i]
if (!dep.has(parentRunner)) {
dep.add(parentRunner)
parentRunner.deps.push(dep)
}
}
}
}
由于computed是依賴了state中的屬性的,一旦在初始時(shí)觸發(fā)了get,執(zhí)行runner,就會(huì)將依賴收集到activeReactiveEffectStack中,最后才是自己的依賴,棧的頂部是state屬性的依賴
if (!dep.has(parentRunner)) {
dep.add(parentRunner)
parentRunner.deps.push(dep)
}
所以最后這段代碼實(shí)現(xiàn)了state屬性變化后,才導(dǎo)致了computed依賴的調(diào)用,從而惰性求值
ref
const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
export function ref<T>(raw: T): Ref<T> {
raw = convert(raw)
const v = {
_isRef: true,
get value() {
track(v, OperationTypes.GET, '')
return raw
},
set value(newVal) {
raw = convert(newVal)
trigger(v, OperationTypes.SET, '')
}
}
return v as Ref<T>
}
ref的實(shí)現(xiàn)真的很簡(jiǎn)單了,前面已經(jīng)學(xué)習(xí)了那么多,相信大家都能看懂了,區(qū)別就是convert(raw)對(duì)傳入的值進(jìn)行了簡(jiǎn)單判斷,如果是對(duì)象就設(shè)置為響應(yīng)式,否則返回原始值。
最后
終于分析完了,Vue3.0響應(yīng)系統(tǒng)使用了Proxy相比于Vue2.0的代碼真的簡(jiǎn)潔許多,也好理解,說(shuō)難不難。其實(shí)還有watch并沒(méi)有講,它沒(méi)有在reactivity中,但是實(shí)現(xiàn)還是使用了effect,套路都是一樣的。最后謝謝大家觀看。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- vue3 響應(yīng)式對(duì)象如何實(shí)現(xiàn)方法的不同點(diǎn)
- 淺析vue3響應(yīng)式數(shù)據(jù)與watch屬性
- vue3中defineProps傳值使用ref響應(yīng)式失效詳解
- vue3.0響應(yīng)式函數(shù)原理詳細(xì)
- vue3.x源碼剖析之?dāng)?shù)據(jù)響應(yīng)式的深入講解
- 詳解Vue3的響應(yīng)式原理解析
- setup+ref+reactive實(shí)現(xiàn)vue3響應(yīng)式功能
- 一文帶你了解vue3.0響應(yīng)式
- 手寫(xiě)?Vue3?響應(yīng)式系統(tǒng)(核心就一個(gè)數(shù)據(jù)結(jié)構(gòu))
相關(guān)文章
vue中v-model指令與.sync修飾符的區(qū)別詳解
本文主要介紹了vue中v-model指令與.sync修飾符的區(qū)別詳解,詳細(xì)的介紹了兩個(gè)的用法和區(qū)別,感興趣的可以了解一下2021-08-08
element組件el-date-picker禁用當(dāng)前時(shí)分秒之前的日期時(shí)間選擇
本文主要介紹了element組件el-date-picker禁用當(dāng)前時(shí)分秒之前的日期時(shí)間選擇,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
從零開(kāi)始實(shí)現(xiàn)Vue簡(jiǎn)單的Toast插件
這篇文章主要給大家介紹了如何從零開(kāi)始實(shí)現(xiàn)Vue簡(jiǎn)單的Toast插件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
使用Vue完成一個(gè)簡(jiǎn)單的todolist的方法
本篇文章主要介紹了使用Vue完成一個(gè)簡(jiǎn)單的todolist的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
vue.js內(nèi)部自定義指令與全局自定義指令的實(shí)現(xiàn)詳解(利用directive)
這篇文章主要給大家介紹了關(guān)于vue.js內(nèi)部自定義指令與全局自定義指令的實(shí)現(xiàn)方法,vue.js中實(shí)現(xiàn)自定義指令的主要是利用directive,directive這個(gè)單詞是我們寫(xiě)自定義指令的關(guān)鍵字,需要的朋友們下面跟著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
基于ant-design-vue實(shí)現(xiàn)表格操作按鈕組件
這篇文章主要為大家介紹了基于ant-design-vue實(shí)現(xiàn)表格操作按鈕組件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Vue 指令實(shí)現(xiàn)按鈕級(jí)別權(quán)限管理功能
這篇文章主要介紹了Vue 指令實(shí)現(xiàn)按鈕級(jí)別權(quán)限管理功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04

