關於響應式原理想必你們都很清楚了,下面我將會根據響應式API來具體講解Vue3.0中的實現原理, 另外我只會針對get
,set
進行深刻分析,本文包含如下API實現,推薦你們順序閱讀javascript
對了,你們必定要先知道怎麼用哦~html
先來段代碼,你們能夠直接複製哦,注意引用的文件vue
<!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>
複製代碼
這段代碼,想必你們都看得懂,點擊後count
增長,視圖也隨之更新,effect
監聽了count
改變,那麼爲何effect
能觀察到count
變化呢,還有爲何reactive
能夠實現響應式?java
爲何要先說這個函數呢,由於它和其餘函數都息息相關,只有先了解它才能更好的理解其餘響應式APInode
上源碼react
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
函數,它已是effect
了,也就是一個標識,直接獲取該函數上的raw
屬性,這個屬性後面會講到api
調用createReactiveEffect
數組
若是options
中有lazy
,就會當即調用effect
,其實本質上調用的仍是傳入的fn
函數數據結構
// 瞭解一下options有哪些
{
lazy?: boolean // 是否當即調用fn
computed?: boolean // 是不是computed
scheduler?: (run: Function) => void // 在調用fn以前執行
onTrack?: (event: DebuggerEvent) => void // 在依賴收集完成以後調用
onTrigger?: (event: DebuggerEvent) => void // 在調用fn以前執行,源碼上來看和scheduler調用時機同樣,只是傳入參數不一樣
onStop?: () => void // 清除依賴完成後調用
}
複製代碼
返回effect
app
上面提到了createReactiveEffect
函數,咱們來看看它的實現
function createReactiveEffect( fn: Function, options: ReactiveEffectOptions ): ReactiveEffect {
// 又包裝了一層函數
const effect = function effect(...args): any {
return run(effect as ReactiveEffect, fn, args)
} as ReactiveEffect
effect.isEffect = true // 標識effect
effect.active = true // 若是active
effect.raw = fn // 傳入的回調
effect.scheduler = options.scheduler
effect.onTrack = options.onTrack
effect.onTrigger = options.onTrigger
effect.onStop = options.onStop
effect.computed = options.computed
effect.deps = [] // 用於收集依賴
return effect
}
複製代碼
注意,敲黑板,這裏有個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()
}
}
}
複製代碼
他把依賴存儲在了一個全局的數組中activeReactiveEffectStack
, 他以棧的形式存儲,調用完依賴後,會彈出,你們要留意一下這裏,後面會用到
怎麼樣,是否是很簡單~
export function reactive(target: object) {
// 若是target是已經被readonly對象,那麼直接返回對應的proxy對象
if (readonlyToRaw.has(target)) {
return target
}
// 若是target是已經被readonly對象,那麼直接返回對應的真實對象
if (readonlyValues.has(target)) {
return readonly(target)
}
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}
複製代碼
if
是用來處理這種狀況的// 狀況一
const state1 = readonly({ count: 0 })
const state2 = reactive(state1)
// 狀況二
const obj = { count: 0 }
const state1 = readonly(obj)
const state2 = reactive(obj)
複製代碼
能夠看到reactive
它的參數是被readonly
的對象,reactive
不會對它再次建立響應式,而是經過Map
映射,拿到對應的對象,即Proxy <==> Object
的相互轉換。
createReactiveObject
建立響應式對象,注意它的參數
createReactiveObject(
target,
rawToReactive, // Object ==> Proxy
reactiveToRaw, // Proxy ==> Object
mutableHandlers, // get set has ...
mutableCollectionHandlers // 不多會用,不講了~
)
複製代碼
以上就是reative
一開始所作的一些事情,下面繼續分析createReactiveObject
function createReactiveObject( target: any, toProxy: WeakMap<any, any>, toRaw: WeakMap<any, any>, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
// 若是不是對象,在開發環境報出警告
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
let observed = toProxy.get(target)
// 若是目標對象已經有proxy對象,直接返回
if (observed !== void 0) {
return observed
}
// 若是目標對象是proxy的對象,而且有對應的真實對象,那麼也直接返回
if (toRaw.has(target)) {
return target
}
// 若是它是vnode或者vue,則不能被觀測
if (!canObserve(target)) {
return target
}
// 判斷被觀測的對象是不是set,weakSet,map,weakMap,根據狀況使用對應proxy的,配置對象
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
}
複製代碼
第一個if
,判斷是不是對象,不然報出警告
toProxy
拿到觀測對象的Proxy
對象,若是存在直接返回
// 這種狀況
const obj = { count: 0 }
const state1 = reative(obj)
const state2 = reative(obj)
複製代碼
toRaw
拿到Proxy
對象對應的真實對象,若是存在直接返回
// 這種狀況
const obj = { count: 0 }
const state1 = reative(obj)
const state2 = reative(state1)
複製代碼
有些狀況沒法被觀測,則直接返回觀測對象自己
const canObserve = (value: any): boolean => {
return (
!value._isVue &&
!value._isVNode &&
observableValueRE.test(toTypeString(value)) &&
!nonReactiveValues.has(value)
)
}
複製代碼
設置handlers
,即get,set
等屬性訪問器, 注意:collectionHandlers是用來處理觀測對象爲Set,Map等狀況,不多見,這裏就不講了
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
複製代碼
而後建立了Proxy
對象,並把觀測對象和Proxy
對象,分別作映射
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())
}
複製代碼
在這裏先給你們賣個關子,targetMap
很是重要,是用來保存依賴的地方
講完了reactive,能夠回到一開始的引子
說到依賴收集,不得不提到,依賴的建立,那麼Vue3.0是在哪裏建立了渲染依賴呢,你們能夠找到下面這段代碼以及文件
// 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)
}
複製代碼
代碼特別長,我剪掉了中間部分,你們還記得effect
有個選項lazy
嗎,沒錯,它默認是false
,也就會當即調用傳入的componentEffect
回調,在它內部調用了patch
實現了組件的掛載。
敲黑板,關鍵來了,還記得effect
調用,內部會調用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()
}
}
}
複製代碼
這裏進行了第一步的依賴收集,保存在全局數組中,爲了方便觸發get
的對象,將依賴收集到本身的deps
中
而後就是調用patch
,進行組件掛載
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
}
複製代碼
至於它內部實現,我就不講了,不是本文重點,而後咱們去編譯的地方看看
//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
}
// ...其餘
}
複製代碼
上面的代碼是編譯部分,咱們來看看例子中編譯後是什麼樣
(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
,那麼就會觸發get
訪問器,從而收集依賴,至於爲何能直接訪問到屬性,緣由是因爲with
設置了上下文,下面咱們具體分析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)
// 若是該屬性對應的值仍是對象,就繼續遞歸建立響應式
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
// circular dependency
readonly(res)
: reactive(res)
: res
}
}
複製代碼
調用Reflect.get
獲取屬性值
若是key
是symbol
而且是Symbol
的一個屬性,就直接返回該值
// 這種狀況
const key = Symbol('key')
const state = reative({
[key]: 'symbol value'
})
state[key]
複製代碼
若是值爲Ref
返回該值的value
,看到這裏若是你們有了解過ref api
的話就知道了,因爲ref
它本身實現了本身的get
,set
,因此再也不須要執行後面的邏輯,這個在後面會講
調用track
遞歸深度觀測,使整個對象都爲響應式
下面我會詳細講解
在講它以前,先了解它有哪些參數
target: any, // 目標對象
type: OperationTypes, // 追蹤數據變化類型,這裏是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
}
// 獲取目標對象對應的依賴map
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
// 獲取對應屬性的依賴
let dep = depsMap.get(key as string | symbol)
// 若是該依賴不存在
if (!dep) {
// 設置屬性對應依賴
depsMap.set(key as string | symbol, (dep = new Set()))
}
// 若是屬性對應依賴set中不存在該依賴
if (!dep.has(effect)) {
// 添加到依賴set中
dep.add(effect)
effect.deps.push(dep)
if (__DEV__ && effect.onTrack) {
// 調用onTrack鉤子
effect.onTrack({
effect,
target,
type,
key
})
}
}
}
}
複製代碼
activeReactiveEffectStack我兩次提到,從它這裏拿到了依賴,注意後面執行完依賴後,會從它裏面彈出
若是effect
存在
從targetMap
中獲取對象,對飲的Map
,具體的數據結構相似這樣
const state = reative({
count: 0
})
effect(() => {
console.log(state.count)
})
// 依賴大體結構(隨便寫的,不太規範)
{
target(state):Map {
count: Set (componentEffect渲染依賴, user本身添加的依賴)
}
}
複製代碼
若是該對象不存在Map
,就初始化一個
若是該Map
中屬性對應的Set
不存在,就初始化一個Set
添加依賴到Set
中
添加依賴到effect
自身的deps
數組中
最後調用onTrack
回調
// 調用onTrack鉤子
effect.onTrack({
effect,
target,
type,
key
})
複製代碼
OK,Track
實現大致就這樣,是否是也很簡單,有了這些基礎,後面要講的一些API就很容易理解了
當咱們點擊按鈕後,就會觸發set
屬性訪問器
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
// 其實state.count最終仍是ref,仍是能經過value訪問
state.count.value // 1
複製代碼
調用Reflect.set
修改值
開發環境下,拿到新舊值組成的對象,調用trigger
,爲何開發環境要這麼作呢,實際上是爲了方便onTrigger
能拿到新舊值
trigger(target, OperationTypes.ADD, key, extraInfo)
複製代碼
能夠看到第二個參數和track
是同樣的enum
,有兩種狀況,一種咱們設置了新的屬性和值,另外一種修改了原有屬性值,下面咱們來看看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))
}
}
// 執行set中的effect
const run = (effect: ReactiveEffect) => {
scheduleRun(effect, target, type, key, extraInfo)
}
computedRunners.forEach(run)
effects.forEach(run)
}
複製代碼
看到這個函數開始的targetMap
,你們應該很清楚要幹嗎了吧,沒錯,拿到對象的Map
,它包含了屬性的全部依賴
若是沒有Map
直接返回
建立了兩個Set
,要幹嗎用呢
// 用來保存將要執行的依賴
const effects: Set<ReactiveEffect> = new Set()
// computed依賴,由於trigger不只是要處理effect,watch,還要處理computed惰性求值的狀況
const computedRunners: Set<ReactiveEffect> = new Set()
複製代碼
處理三種狀況CLEAR
,ADD
,DELETE
,SET(這裏沒有標識)
// 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)
}
})
}
}
複製代碼
能夠看到,三種狀況實際上都差很少,惟一的區別就是,若是添加的對象是數組,就會拿到length
屬性的依賴,用於修改數組長度
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
addRunners(effects, computedRunners, depsMap.get(iterationKey))
}
複製代碼
執行屬性對應的依賴
// 執行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()
}
}
複製代碼
最後調用了scheduleRun
,它內部會分別執行onTrigger
,scheduler
,effect
須要注意的是,只有開發環境纔會執行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
看起來會很是簡單,惟一的區別就是rawToReadonly
,rawToReadonly
, readonlyHandlers
export function readonly(target: object) {
if (reactiveToRaw.has(target)) {
target = reactiveToRaw.get(target)
}
return createReactiveObject(
target,
rawToReadonly,
readonlyToRaw,
readonlyHandlers,
readonlyCollectionHandlers
)
}
複製代碼
前兩個你們應該能猜出來了,關鍵是最後這個readonlyHandlers
,區別就在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)
}
}
複製代碼
它的實現很簡單,不過LOCKED
有是什麼鬼,你們能夠找到lock.ts
//vue-next\packages\reactivity\src\lock.ts
export let LOCKED = true
export function lock() {
LOCKED = true
}
export function unlock() {
LOCKED = false
}
複製代碼
看似簡單,可是卻很是重要,它可以控制被readonly
的對象可以暫時被更改,就好比咱們經常使用的props
,它是沒法被修改的,可是Vue內部又要對他進行更新,那怎麼辦,話很少說,咱們再源碼中看他具體應用
// 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
}
複製代碼
這裏先後分別調用了unlock
和lock
,這樣就能夠控制對readonly
屬性的修改
那麼readonly
的講解就到這了
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
是能夠單獨寫一個函數,或者get
,set
訪問的,這裏很少講
而後調用了effect
,這裏lazy設置爲true
, scheduler能夠更改dirty
爲true
const runner = effect(getter, {
lazy: true,
computed: true,
scheduler: () => {
dirty = true
}
})
複製代碼
而後咱們具體來看看,返回的對象
{
_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
}
}
}
複製代碼
先說說set
吧,尤大彷佛還沒寫完,只是單純能修改值
而後是get
,注意dirty
的變化,若是computed
依賴了state
中的值,初次渲染時,他會調用依賴,而後dirty = false
,關鍵來了,最後執行了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
中的屬性的,一旦在初始時觸發了get
,執行runner
,就會將依賴收集到activeReactiveEffectStack
中,最後纔是本身的依賴,棧的頂部是state
屬性的依賴
if (!dep.has(parentRunner)) {
dep.add(parentRunner)
parentRunner.deps.push(dep)
}
複製代碼
因此最後這段代碼實現了state
屬性變化後,才致使了computed
依賴的調用,從而惰性求值
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
的實現真的很簡單了,前面已經學習了那麼多,相信你們都能看懂了,區別就是convert(raw)
對傳入的值進行了簡單判斷,若是是對象就設置爲響應式,不然返回原始值。
終於分析完了,Vue3.0響應系統使用了Proxy
相比於Vue2.0的代碼真的簡潔許多,也好理解,說難不難。其實還有watch
並無講,它沒有在reactivity
中,可是實現仍是使用了effect
,套路都是同樣的。最後謝謝你們觀看。