前言
關(guān)于響應(yīng)式原理想必大家都很清楚了,下面我將會根據(jù)響應(yīng)式API來具體講解Vue3.0中的實現(xiàn)原理, 另外我只會針對get,set進行深入分析,本文包含以下API實現(xiàn),推薦大家順序閱讀
effect
reactive
readonly
computed
ref
對了,大家一定要先知道怎么用哦~
引子
先來段代碼,大家可以直接復(fù)制哦,注意引用的文件
? ? ? ? Document? ?
? ? ? const { reactive, computed, effect, watch, createApp } = Vue? ? const App = {? ? ? template: `? ? ? ?這段代碼,想必大家都看得懂,點擊后count增加,視圖也隨之更新,effect監(jiān)聽了count改變,那么為什么effect能觀察到count變化呢,還有為什么reactive可以實現(xiàn)響應(yīng)式?
effect
為什么要先說這個函數(shù)呢,因為它和其他函數(shù)都息息相關(guān),只有先了解它才能更好的理解其他響應(yīng)式API
上源碼
exportfunctioneffect(? fn: Function,
? options: ReactiveEffectOptions = EMPTY_OBJ):ReactiveEffect{if((fnasReactiveEffect).isEffect) {? ? fn = (fnasReactiveEffect).raw? }consteffect = createReactiveEffect(fn, options)if(!options.lazy) {? ? effect()? }returneffect}復(fù)制代碼
if判斷,判斷如果傳入的fn函數(shù),它已經(jīng)是effect了,也就是一個標(biāo)識,直接獲取該函數(shù)上的raw屬性,這個屬性后面會講到
調(diào)用createReactiveEffect
如果options中有l(wèi)azy,就會立即調(diào)用effect,其實本質(zhì)上調(diào)用的還是傳入的fn函數(shù)
// 了解一下options有哪些{? lazy?: boolean// 是否立即調(diào)用fncomputed?: boolean// 是否是computedscheduler?:(run:Function) =>void// 在調(diào)用fn之前執(zhí)行onTrack?:(event: DebuggerEvent) =>void// 在依賴收集完成之后調(diào)用onTrigger?:(event: DebuggerEvent) =>void// 在調(diào)用fn之前執(zhí)行,源碼上來看和scheduler調(diào)用時機一樣,只是傳入?yún)?shù)不同onStop?:()=>void// 清除依賴完成后調(diào)用}復(fù)制代碼
返回effect
createReactiveEffect
上面提到了createReactiveEffect函數(shù),我們來看看它的實現(xiàn)
functioncreateReactiveEffect(? fn: Function,
? options: ReactiveEffectOptions):ReactiveEffect{// 又包裝了一層函數(shù)consteffect =functioneffect(...args):any{returnrun(effectasReactiveEffect, fn, args)? }asReactiveEffect? effect.isEffect =true// 標(biāo)識effecteffect.active =true// 如果activeeffect.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 = []// 用于收集依賴returneffect}復(fù)制代碼
注意,敲黑板,這里有個run函數(shù),很重要,因為它保存了依賴
functionrun(effect: ReactiveEffect, fn: Function, args: any[]):any{if(!effect.active) {returnfn(...args)? }if(activeReactiveEffectStack.indexOf(effect) ===-1) {? ? cleanup(effect)try{? ? ? activeReactiveEffectStack.push(effect)returnfn(...args)? ? }finally{? ? ? activeReactiveEffectStack.pop()? ? }? }}復(fù)制代碼
他把依賴存儲在了一個全局的數(shù)組中activeReactiveEffectStack, 他以棧的形式存儲,調(diào)用完依賴后,會彈出,大家要留意一下這里,后面會用到
怎么樣,是不是很簡單~
reactive
exportfunctionreactive(target: object){// 如果target是已經(jīng)被readonly對象,那么直接返回對應(yīng)的proxy對象if(readonlyToRaw.has(target)) {returntarget? }// 如果target是已經(jīng)被readonly對象,那么直接返回對應(yīng)的真實對象if(readonlyValues.has(target)) {returnreadonly(target)? }returncreateReactiveObject(? ? target,? ? rawToReactive,? ? reactiveToRaw,? ? mutableHandlers,? ? mutableCollectionHandlers? )}復(fù)制代碼
前兩個if是用來處理這種情況的
// 情況一conststate1 = readonly({count:0})conststate2 = reactive(state1)// 情況二constobj = {count:0}conststate1 = readonly(obj)conststate2 = reactive(obj)復(fù)制代碼
可以看到reactive它的參數(shù)是被readonly的對象,reactive不會對它再次創(chuàng)建響應(yīng)式,而是通過Map映射,拿到對應(yīng)的對象,即Proxy <==> Object的相互轉(zhuǎn)換。
createReactiveObject創(chuàng)建響應(yīng)式對象,注意它的參數(shù)
createReactiveObject(? ? target,? ? rawToReactive,// Object ==> ProxyreactiveToRaw,// Proxy ==> ObjectmutableHandlers,// get set has ...mutableCollectionHandlers// 很少會用,不講了~)復(fù)制代碼
以上就是reative一開始所做的一些事情,下面繼續(xù)分析createReactiveObject
createReactiveObject
functioncreateReactiveObject(? target: any,
? toProxy: WeakMap<any, any>,
? toRaw: WeakMap<any, any>,
? baseHandlers: ProxyHandler<any>,
? collectionHandlers: ProxyHandler<any>){// 如果不是對象,在開發(fā)環(huán)境報出警告if(!isObject(target)) {if(__DEV__) {console.warn(`value cannot be made reactive:${String(target)}`)? ? }returntarget? }letobserved = toProxy.get(target)// 如果目標(biāo)對象已經(jīng)有proxy對象,直接返回if(observed !==void0) {returnobserved? }// 如果目標(biāo)對象是proxy的對象,并且有對應(yīng)的真實對象,那么也直接返回if(toRaw.has(target)) {returntarget? }// 如果它是vnode或者vue,則不能被觀測if(!canObserve(target)) {returntarget? }// 判斷被觀測的對象是否是set,weakSet,map,weakMap,根據(jù)情況使用對應(yīng)proxy的,配置對象consthandlers = collectionTypes.has(target.constructor)? ? ? collectionHandlers? ? : baseHandlers? observed =newProxy(target, handlers)? toProxy.set(target, observed)? toRaw.set(observed, target)if(!targetMap.has(target)) {? ? targetMap.set(target,newMap())? }returnobserved}復(fù)制代碼
第一個if,判斷是否是對象,否則報出警告
toProxy拿到觀測對象的Proxy對象,如果存在直接返回
// 這種情況constobj = {count:0}conststate1 = reative(obj)conststate2 = reative(obj)復(fù)制代碼
toRaw拿到Proxy對象對應(yīng)的真實對象,如果存在直接返回
// 這種情況constobj = {count:0}conststate1 = reative(obj)conststate2 = reative(state1)復(fù)制代碼
有些情況無法被觀測,則直接返回觀測對象本身
constcanObserve = (value: any):boolean=>{return(? ? !value._isVue &&? ? !value._isVNode &&? ? observableValueRE.test(toTypeString(value)) &&? ? !nonReactiveValues.has(value)? )}復(fù)制代碼
設(shè)置handlers,即get,set等屬性訪問器,?注意:collectionHandlers是用來處理觀測對象為Set,Map等情況,很少見,這里就不講了
consthandlers = collectionTypes.has(target.constructor)? ? ? collectionHandlers? ? : baseHandlers復(fù)制代碼
然后創(chuàng)建了Proxy對象,并把觀測對象和Proxy對象,分別做映射
observed =newProxy(target, handlers)? toProxy.set(target, observed)? toRaw.set(observed, target)復(fù)制代碼
然后在targetMap做了target ==> Map的映射,這又是干嘛,注意:targetMap是全局的
exportconsttargetMap:WeakMap =newWeakMap()if(!targetMap.has(target)) {? ? targetMap.set(target,newMap())? }復(fù)制代碼
在這里先給大家賣個關(guān)子,targetMap非常重要,是用來保存依賴的地方
講完了reactive,可以回到一開始的引子
依賴收集
說到依賴收集,不得不提到,依賴的創(chuàng)建,那么Vue3.0是在哪里創(chuàng)建了渲染依賴呢,大家可以找到下面這段代碼以及文件
// vue-next\packages\runtime-core\src\createRenderer.tsfunctionsetupRenderEffect(? ? instance: ComponentInternalInstance,
? ? parentSuspense: HostSuspsenseBoundary | null,
? ? initialVNode: HostVNode,
? ? container: HostElement,
? ? anchor: HostNode | null,
? ? isSVG: boolean
? ){// create reactive effect for renderingletmounted =falseinstance.update = effect(functioncomponentEffect(){// ...}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)? }復(fù)制代碼
代碼特別長,我剪掉了中間部分,大家還記得effect有個選項lazy嗎,沒錯,它默認是false,也就會立即調(diào)用傳入的componentEffect回調(diào),在它內(nèi)部調(diào)用了patch實現(xiàn)了組件的掛載。
敲黑板,關(guān)鍵來了,還記得effect調(diào)用,內(nèi)部會調(diào)用run方法嗎
functionrun(effect: ReactiveEffect, fn: Function, args: any[]):any{if(!effect.active) {returnfn(...args)? }if(activeReactiveEffectStack.indexOf(effect) ===-1) {? ? cleanup(effect)try{? ? ? activeReactiveEffectStack.push(effect)returnfn(...args)? ? }finally{? ? ? activeReactiveEffectStack.pop()? ? }? }}復(fù)制代碼
這里進行了第一步的依賴收集,保存在全局數(shù)組中,為了方便觸發(fā)get的對象,將依賴收集到自己的deps中
然后就是調(diào)用patch,進行組件掛載
if(!mounted) {constsubTree = (instance.subTree = renderComponentRoot(instance))// beforeMount hookif(instance.bm !==null) {? ? ? ? invokeHooks(instance.bm)? ? }? ? patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)? ? initialVNode.el = subTree.el// mounted hookif(instance.m !==null) {? ? ? ? queuePostRenderEffect(instance.m, parentSuspense)? ? }? ? mounted =true}復(fù)制代碼
至于它內(nèi)部實現(xiàn),我就不講了,不是本文重點,然后我們?nèi)ゾ幾g的地方看看
//vue-next\packages\runtime-core\src\component.tsfunctionfinishComponentSetup(? instance: ComponentInternalInstance,
? parentSuspense: SuspenseBoundary | null){constComponent = instance.typeasComponentOptionsif(!instance.render) {if(Component.template && !Component.render) {if(compile) {? ? ? ? Component.render = compile(Component.template, {? ? ? ? ? onError(err) {}? ? ? ? })? ? ? }elseif(__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)asRenderFunction? }// ...其他}復(fù)制代碼
上面的代碼是編譯部分,我們來看看例子中編譯后是什么樣
(functionanonymous(){const_Vue = Vueconst_createVNode = Vue.createVNodeconst_hoisted_1 = {id:"box"}returnfunctionrender(){with(this) {const{toString: _toString,createVNode: _createVNode,openBlock: _openBlock,createBlock: _createBlock } = _Vuereturn(_openBlock(), _createBlock("div", _hoisted_1, [? ? ? _createVNode("button", {onClick: increment }, _toString(state.count),9/* TEXT, PROPS */, ["onClick"])? ? ]))? }}})復(fù)制代碼
可以看到,編譯的代碼中,有使用到state.count,那么就會觸發(fā)get訪問器,從而收集依賴,至于為什么能直接訪問到屬性,原因是由于with設(shè)置了上下文,下面我們具體分析get
get
// vue-next\packages\reactivity\src\baseHandlers.tsfunctioncreateGetter(isReadonly: boolean){returnfunctionget(target: any, key: string | symbol, receiver: any){constres =Reflect.get(target, key, receiver)if(typeofkey ==='symbol'&& builtInSymbols.has(key)) {returnres? ? }// _isRefif(isRef(res)) {returnres.value? ? }? ? track(target, OperationTypes.GET, key)// 如果該屬性對應(yīng)的值還是對象,就繼續(xù)遞歸創(chuàng)建響應(yīng)式returnisObject(res)? ? ? ? isReadonly? ? ? ? ?// need to lazy access readonly and reactive here to avoid// circular dependencyreadonly(res)? ? ? ? : reactive(res)? ? ? : res? }}復(fù)制代碼
調(diào)用Reflect.get獲取屬性值
如果key是symbol并且是Symbol的一個屬性,就直接返回該值
// 這種情況constkey =Symbol('key')conststate = reative({? ? [key]:'symbol value'})state[key]復(fù)制代碼
如果值為Ref返回該值的value,看到這里如果大家有了解過ref api的話就知道了,由于ref它自己實現(xiàn)了自己的get,set,所以不再需要執(zhí)行后面的邏輯,這個在后面會講
調(diào)用track
遞歸深度觀測,使整個對象都為響應(yīng)式
下面我會詳細講解
track
在講它之前,先了解它有哪些參數(shù)
target: any,// 目標(biāo)對象type: OperationTypes,// 追蹤數(shù)據(jù)變化類型,這里是getkey?: string | symbol// 需要獲取的keyexportconstenum OperationTypes {? ? ? SET ='set',? ? ? ADD ='add',? ? ? DELETE ='delete',? ? ? CLEAR ='clear',? ? ? GET ='get',? ? ? HAS ='has',? ? ? ITERATE ='iterate'}復(fù)制代碼
exportfunctiontrack(? target: any,
? type: OperationTypes,
? key?: string | symbol){if(!shouldTrack) {return}// 獲取activeReactiveEffectStack中的依賴consteffect = activeReactiveEffectStack[activeReactiveEffectStack.length -1]if(effect) {if(type === OperationTypes.ITERATE) {? ? ? key = ITERATE_KEY? ? }// 獲取目標(biāo)對象對應(yīng)的依賴mapletdepsMap = targetMap.get(target)if(depsMap ===void0) {? ? ? targetMap.set(target, (depsMap =newMap()))? ? }// 獲取對應(yīng)屬性的依賴letdep = depsMap.get(keyasstring | symbol)// 如果該依賴不存在if(!dep) {// 設(shè)置屬性對應(yīng)依賴depsMap.set(keyasstring | symbol, (dep =newSet()))? ? }// 如果屬性對應(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? ? ? ? })? ? ? }? ? }? }}復(fù)制代碼
activeReactiveEffectStack我兩次提到,從它這里拿到了依賴,注意后面執(zhí)行完依賴后,會從它里面彈出
如果effect存在
從targetMap中獲取對象,對飲的Map,具體的數(shù)據(jù)結(jié)構(gòu)類似這樣
conststate = reative({count:0})effect(()=>{console.log(state.count)? })// 依賴大致結(jié)構(gòu)(隨便寫的,不太規(guī)范){? ? target(state):Map{count:Set(componentEffect渲染依賴, user自己添加的依賴)? ? }}復(fù)制代碼
如果該對象不存在Map,就初始化一個
如果該Map中屬性對應(yīng)的Set不存在,就初始化一個Set
添加依賴到Set中
添加依賴到effect自身的deps數(shù)組中
最后調(diào)用onTrack回調(diào)
// 調(diào)用onTrack鉤子effect.onTrack({? ? effect,? ? target,? ? type,? ? key})復(fù)制代碼
OK,Track實現(xiàn)大體就這樣,是不是也很簡單,有了這些基礎(chǔ),后面要講的一些API就很容易理解了
set
當(dāng)我們點擊按鈕后,就會觸發(fā)set屬性訪問器
functionset(? target: any,
? key: string | symbol,
? value: any,
? receiver: any):boolean{? value = toRaw(value)consthadKey = hasOwn(target, key)constoldValue = target[key]// 如果舊的值是ref,而新的值不是refif(isRef(oldValue) && !isRef(value)) {// 直接更改原始ref即可oldValue.value = valuereturntrue}constresult =Reflect.set(target, key, value, receiver)// don't trigger if target is something up in the prototype chain of originalif(target === toRaw(receiver)) {/* istanbul ignore else */if(__DEV__) {constextraInfo = { oldValue,newValue: value }if(!hadKey) {? ? ? ? trigger(target, OperationTypes.ADD, key, extraInfo)? ? ? }elseif(value !== oldValue) {? ? ? ? trigger(target, OperationTypes.SET, key, extraInfo)? ? ? }? ? }else{if(!hadKey) {? ? ? ? trigger(target, OperationTypes.ADD, key)? ? ? }elseif(value !== oldValue) {? ? ? ? trigger(target, OperationTypes.SET, key)? ? ? }? ? }? }returnresult}復(fù)制代碼
判斷舊值是ref,新值不是ref
// 這種情況constval = ref(0)conststate = reative({count: val})state.count =1// 其實state.count最終還是ref,還是能通過value訪問state.count.value// 1復(fù)制代碼
調(diào)用Reflect.set修改值
開發(fā)環(huán)境下,拿到新舊值組成的對象,調(diào)用trigger,為什么開發(fā)環(huán)境要這么做呢,其實是為了方便onTrigger能拿到新舊值
trigger(target, OperationTypes.ADD, key, extraInfo)復(fù)制代碼
可以看到第二個參數(shù)和track是一樣的enum,有兩種情況,一種我們設(shè)置了新的屬性和值,另一種修改了原有屬性值,下面我們來看看trigger實現(xiàn)。
trigger
exportfunctiontrigger(? target: any,
? type: OperationTypes,
? key?: string | symbol,
? extraInfo?: any){constdepsMap = targetMap.get(target)if(depsMap ===void0) {// never been trackedreturn}// effect setconsteffects:Set =newSet()// computed effect setconstcomputedRunners:Set =newSet()if(type === OperationTypes.CLEAR) {? ? depsMap.forEach(dep=>{? ? ? addRunners(effects, computedRunners, dep)? ? })? }else{// 添加effect到set中if(key !==void0) {? ? ? addRunners(effects, computedRunners, depsMap.get(keyasstring | symbol))? ? }// also run for iteration key on ADD | DELETEif(type === OperationTypes.ADD || type === OperationTypes.DELETE) {constiterationKey =Array.isArray(target) ?'length': ITERATE_KEY? ? ? addRunners(effects, computedRunners, depsMap.get(iterationKey))? ? }? }// 執(zhí)行set中的effectconstrun =(effect: ReactiveEffect) =>{? ? scheduleRun(effect, target, type, key, extraInfo)? }? computedRunners.forEach(run)? effects.forEach(run)}復(fù)制代碼
看到這個函數(shù)開始的targetMap,大家應(yīng)該很清楚要干嘛了吧,沒錯,拿到對象的Map,它包含了屬性的所有依賴
如果沒有Map直接返回
創(chuàng)建了兩個Set,要干嘛用呢
// 用來保存將要執(zhí)行的依賴consteffects:Set =newSet()// computed依賴,因為trigger不僅是要處理effect,watch,還要處理computed惰性求值的情況constcomputedRunners:Set =newSet()復(fù)制代碼
處理三種情況CLEAR,ADD,DELETE,SET(這里沒有標(biāo)識)
// effect setconsteffects:Set =newSet()// computed effect setconstcomputedRunners:Set =newSet()functionaddRunners(? effects: Set<ReactiveEffect>,
? computedRunners: Set<ReactiveEffect>,
? effectsToAdd: Set<ReactiveEffect> | undefined){if(effectsToAdd !==void0) {? ? effectsToAdd.forEach(effect=>{if(effect.computed) {? ? ? ? computedRunners.add(effect)? ? ? }else{? ? ? ? effects.add(effect)? ? ? }? ? })? }}復(fù)制代碼
可以看到,三種情況實際上都差不多,唯一的區(qū)別就是,如果添加的對象是數(shù)組,就會拿到length屬性的依賴,用于修改數(shù)組長度
if(type === OperationTypes.ADD || type === OperationTypes.DELETE) {constiterationKey =Array.isArray(target) ?'length': ITERATE_KEY? ? addRunners(effects, computedRunners, depsMap.get(iterationKey))}復(fù)制代碼
執(zhí)行屬性對應(yīng)的依賴
// 執(zhí)行set中的effectconstrun =(effect: ReactiveEffect) =>{? ? scheduleRun(effect, target, type, key, extraInfo)? }? computedRunners.forEach(run)? effects.forEach(run)復(fù)制代碼
functionscheduleRun(? 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 !==void0) {? ? effect.scheduler(effect)? }else{? ? effect()? }}復(fù)制代碼
最后調(diào)用了scheduleRun,它內(nèi)部會分別執(zhí)行onTrigger,scheduler,effect
需要注意的是,只有開發(fā)環(huán)境才會執(zhí)行onTrigger,這也是為什么,前面要這么判斷
if(__DEV__) {constextraInfo = { oldValue,newValue: value }if(!hadKey) {? ? ? ? trigger(target, OperationTypes.ADD, key, extraInfo)? ? }elseif(value !== oldValue) {? ? ? ? trigger(target, OperationTypes.SET, key, extraInfo)? ? }}復(fù)制代碼
readonly
有了前面的基礎(chǔ),readonly看起來會非常簡單,唯一的區(qū)別就是rawToReadonly,rawToReadonly,?readonlyHandlers
exportfunctionreadonly(target: object){if(reactiveToRaw.has(target)) {? ? target = reactiveToRaw.get(target)? }returncreateReactiveObject(? ? target,? ? rawToReadonly,? ? readonlyToRaw,? ? readonlyHandlers,? ? readonlyCollectionHandlers? )}復(fù)制代碼
前兩個大家應(yīng)該能猜出來了,關(guān)鍵是最后這個readonlyHandlers,區(qū)別就在set
set(target: any,key: string | symbol,value: any,receiver: any): boolean {if(LOCKED) {if(__DEV__) {console.warn(`Set operation on key "${keyasany}" failed: target is readonly.`,? ? ? ? target? ? ? )? ? }returntrue}else{returnset(target, key, value, receiver)? } }復(fù)制代碼
它的實現(xiàn)很簡單,不過LOCKED有是什么鬼,大家可以找到lock.ts
//vue-next\packages\reactivity\src\lock.tsexportletLOCKED =trueexportfunctionlock(){? LOCKED =true}exportfunctionunlock(){? LOCKED =false}復(fù)制代碼
看似簡單,但是卻非常重要,它能夠控制被readonly的對象能夠暫時被更改,就比如我們常用的props,它是無法被修改的,但是Vue內(nèi)部又要對他進行更新,那怎么辦,話不多說,我們再源碼中看他具體應(yīng)用
// vue-next\packages\runtime-core\src\componentProps.tsexportfunctionresolveProps(? instance: ComponentInternalInstance,
? rawProps: any,
? _options: ComponentPropsOptions | void){consthasDeclaredProps = _options !=nullconstoptions = normalizePropsOptions(_options)asNormalizedPropsOptionsif(!rawProps && !hasDeclaredProps) {return}constprops: any = {}letattrs: any =void0constpropsProxy = instance.propsProxyconstsetProp = 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}復(fù)制代碼
這里前后分別調(diào)用了unlock和lock,這樣就可以控制對readonly屬性的修改
那么readonly的講解就到這了
computed
exportfunctioncomputed(? getterOrOptions: (() =>T) |WritableComputedOptions):any{constisReadonly = isFunction(getterOrOptions)constgetter = isReadonly? ? ?(getterOrOptionsas(() =>T))? ? : (getterOrOptionsasWritableComputedOptions).getconstsetter = isReadonly? ? ?null: (getterOrOptionsasWritableComputedOptions).setletdirty: boolean =trueletvalue: any =undefinedconstrunner = effect(getter, {lazy:true,computed:true,scheduler:()=>{? ? ? dirty =true}? })return{_isRef:true,// expose effect so computed can be stoppedeffect: runner,? ? get value() {if(dirty) {? ? ? ? value = runner()? ? ? ? dirty =false}? ? ? trackChildRun(runner)returnvalue? ? },? ? set value(newValue) {if(setter) {? ? ? ? setter(newValue)? ? ? }else{// TODO warn attempting to mutate readonly computed value}? ? }? }}復(fù)制代碼
首先是前面這段
constisReadonly = isFunction(getterOrOptions)constgetter = isReadonly? ? ?(getterOrOptionsas(() =>T))? ? : (getterOrOptionsasWritableComputedOptions).getconstsetter = isReadonly? ? ?null: (getterOrOptionsasWritableComputedOptions).set復(fù)制代碼
大家都知道computed是可以單獨寫一個函數(shù),或者get,set訪問的,這里不多講
然后調(diào)用了effect,這里lazy設(shè)置為true, scheduler可以更改dirty為true
construnner = effect(getter, {lazy:true,computed:true,scheduler:()=>{? ? ? ? dirty =true}})復(fù)制代碼
然后我們具體來看看,返回的對象
{_isRef:true,// expose effect so computed can be stoppedeffect: runner,? ? get value() {if(dirty) {? ? ? ? value = runner()? ? ? ? dirty =false}? ? ? trackChildRun(runner)returnvalue? ? },? ? set value(newValue) {if(setter) {? ? ? ? setter(newValue)? ? ? }else{// TODO warn attempting to mutate readonly computed value}? ? }? }復(fù)制代碼
先說說set吧,尤大似乎還沒寫完,只是單純能修改值
然后是get,注意dirty的變化,如果computed依賴了state中的值,初次渲染時,他會調(diào)用依賴,然后dirty = false,關(guān)鍵來了,最后執(zhí)行了trackChildRun
functiontrackChildRun(childRunner: ReactiveEffect){constparentRunner =? ? activeReactiveEffectStack[activeReactiveEffectStack.length -1]if(parentRunner) {for(leti =0; i < childRunner.deps.length; i++) {constdep = childRunner.deps[i]if(!dep.has(parentRunner)) {? ? ? ? dep.add(parentRunner)? ? ? ? parentRunner.deps.push(dep)? ? ? }? ? }? }}復(fù)制代碼
由于computed是依賴了state中的屬性的,一旦在初始時觸發(fā)了get,執(zhí)行runner,就會將依賴收集到activeReactiveEffectStack中,最后才是自己的依賴,棧的頂部是state屬性的依賴
if(!dep.has(parentRunner)) {? ? dep.add(parentRunner)? ? parentRunner.deps.push(dep)}復(fù)制代碼
所以最后這段代碼實現(xiàn)了state屬性變化后,才導(dǎo)致了computed依賴的調(diào)用,從而惰性求值
ref
constconvert = (val: any):any=>(isObject(val) ? reactive(val) : val)exportfunctionref(raw: T):Ref{? raw = convert(raw)constv = {_isRef:true,? ? get value() {? ? ? track(v, OperationTypes.GET,'')returnraw? ? },? ? set value(newVal) {? ? ? raw = convert(newVal)? ? ? trigger(v, OperationTypes.SET,'')? ? }? }returnvasRef}復(fù)制代碼
ref的實現(xiàn)真的很簡單了,前面已經(jīng)學(xué)習(xí)了那么多,相信大家都能看懂了,區(qū)別就是convert(raw)對傳入的值進行了簡單判斷,如果是對象就設(shè)置為響應(yīng)式,否則返回原始值。
最后
終于分析完了,Vue3.0響應(yīng)系統(tǒng)使用了Proxy相比于Vue2.0的代碼真的簡潔許多,也好理解,說難不難。其實還有watch并沒有講,它沒有在reactivity中,但是實現(xiàn)還是使用了effect,套路都是一樣的。最后謝謝大家觀看。