本文是我新開的坑的第一篇文章,這個坑就是vue3,接下來我會圍繞着vue3進行一系列的動做,包括但不限於:html
關於源碼解析,網站已經上線,vue3源碼解析,最佳實踐,網站是逐行代碼形式的解析,更多關注於源碼,而在掘金上分享的文章則相似於總結,會用更復合一篇文章的結構來寫。若是你想持續跟進vue3源碼,能夠打開前面的網站關注我。vue
那麼,開始!react
vue3最大的變化莫過於其對於響應式原理的重構,以及其新發布的composition api
,本文聚焦於前者,來深度剖析一下vue3中響應式究竟是怎麼實現的。api
咱們以reactive
API 爲例,數組
const Comp = {
setup() {
const state = reactive({
a: 'jokcy'
})
return () => {
return <input value={state.a} onChange={(e) => state.a = e.targent.value} /> } } } 複製代碼
咱們看上面的例子,這個例子很簡單,建立了一個組件,他有一個響應式的數據對象,而後render裏面的input
的value綁定的是state.a
以及他的onChange
會修改state.a
。這是很是簡單且直觀的一個數據綁定的例子,而這個邏輯能實現的根本緣由,是咱們在調用state.a = xxx
的時候,vue會從新渲染咱們return
的render函數,來更新節點函數
而篇文章就是要來看一下,咱們經過reactive
建立的對象,到底有什麼魔力,可以幫咱們完成這個任務。網站
其實自己 API 是很簡單的,傳入一個對象,返回一個 reactive 對象,建立的方法是createReactiveObject
ui
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target;
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
);
}
複製代碼
function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
// 前面都是一些對象是否已經proxy等的判斷邏輯,這裏就不展現了
const observed = new Proxy(
target,
collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
);
def(
target,
isReadonly ? ReactiveFlags.READONLY : ReactiveFlags.REACTIVE,
observed
);
return observed;
}
複製代碼
那麼這裏最重要的就是new Proxy
了,能夠說理解 vue3 的響應式原理過程就是理解這個proxy
建立的過程,而瞭解這個過程,主要就是看第二個參數,在這裏就是collectionHandlers
或者baseHandlers
,大部分是後者,前者主要針對,Set、Map 等。spa
那麼咱們就來看baseHandlers
:代理
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys,
};
複製代碼
可見 vue 代理了這幾個操做,那麼咱們一個個看這幾個操做作了啥:
function get(target: object, key: string | symbol, receiver: object) {
// ...內部key的貨足
// 關於數組的一些特殊處理
const targetIsArray = isArray(target);
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
// 獲取請求值
const res = Reflect.get(target, key, receiver);
// ...若是是內部值的獲取則直接返回res
if (!isReadonly) {
track(target, TrackOpTypes.GET, key);
}
// 返回的一些處理
return res;
}
複製代碼
這裏的重點就在於track(target, TrackOpTypes.GET, key);
,這個是咱們正常獲取值的時候都會執行到的代碼:
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key,
});
}
}
}
複製代碼
好了,重點來了,咱們逐行分析。
第一個if
,就是根據環境變量的shouldTrack
來判斷是否要進行跟蹤,若是你已經看過個人源碼解析中的processComponent
的章節,那麼你如今應該就是豁然開朗的感受。由於在執行processComponent
裏面的setup
的時候,咱們特意關閉了track
,而那時候就是把shouldTrack
改成了false
。
let depsMap = targetMap.get(target)
這行,targetMap
是一個 map,用來記錄全部的響應對象。以後若是目前沒有記錄該對象,那麼就從新記錄。
這個 target 對應的也是一個 map,他會對每一個 key 創建一個 set。
最後要記錄此次的 effect 了,這裏所謂的effect
是什麼呢?就是當前正在調用這個對象的函數。在咱們的例子裏面,就是return回去的render
函數,這個函數在執行的時候會調用state.a
因此會進入proxy
對於get
的代理,這個時候 proxy 就調用了track
,那麼這時候的activeEffect
就是這個 render 方法。這裏的effect
就是,當state.a
改動的時候,咱們須要從新執行該 render 方法來進行渲染。
那麼他是何時被設置的呢?在mount
章節的時候咱們提到了,在執行render
方法的時候,咱們執行這樣一句代碼instance.update = effect(function componentEffect()...)
,就是在這裏調用的effect
方法裏面,把activeEffect
記錄爲componentEffect
,而這個componentEffect
裏面則運行了render
方法。
另外這個getHandler
裏面有句代碼也挺有意思的:
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
複製代碼
在獲取屬性的時候,若是返回的值是對象的時候,纔會對其執行reactive
,這也就是延遲處理,並且readonly
的話是不執行reactive
的。
OK,到這裏咱們已經知道了在執行render
函數的時候,由於咱們調用了state.a
因此這個函數就至關於依賴state.a
,這在vue3裏面被稱爲effect
。
那麼下一篇文章,咱們就來說一講,這些effect
在state.a
變更的時候是如何被調用的,敬請期待。