基於Vue3.0發佈在GitHub上的初版源碼(2019.10.05)整理html
先把vue-next倉庫的代碼clone下來,安裝依賴而後構建一下,vue的package下的dist目錄下找到構建的腳本,引入腳本便可。 下面一個簡單計數器的DEMO:vue
<!DOCTYPE html>
<html lang="en">
<body>
<div id='app'></div>
</body>
<script src="./dist/vue.global.js"></script>
<script> const { createApp, reactive, computed } = Vue; const RootComponent = { template: ` <button @click="increment"> Count is: {{ state.count }} </button> `, setup() { const state = reactive({ count: 0, }) function increment() { state.count++ } return { state, increment } } } createApp().mount(RootComponent, '#app') </script>
</html>
複製代碼
template
和以前同樣,一樣Vue3也支持手寫render
的寫法,template
和render
同時存在的狀況,優先render
。react
setup
選項是新增的主要變更,顧名思義,setup
函數會在組件掛載前(beforeCreate
和created
生命週期之間)運行一次,相似組件初始化的做用,setup
須要返回一個對象或者函數。返回對象會被賦值給組件實例的renderContext
,在組件的模板做用域能夠被訪問到,相似data的返回值。返回函數會被當作是組件的render
。具體能夠細看文檔。git
reactive
的做用是將對象包裝成響應式對象,經過Proxy代理後的對象。github
上面的計數器的例子,在組件的setup
函數中,建立了一個響應式對象state
包含一個count
屬性。而後建立了一個increment
遞增的函數,最後將state
和increment
返回給做用域,這樣template
裏的button
按鈕就能訪問到increment
函數綁定到點擊的回調,count
也能顯示在按鈕上。咱們點擊按鈕,按鈕上的數值就能跟着遞增。web
下面切入正題,咱們就來探究下按鈕上count
值跟着響應式更新的原理api
首先列一下主要的一些數據結構,先列在這裏,後面提到能夠翻回來看看。緩存
ReactiveEffect
一個Function
對象,用於執行組件的掛載和更新。數據結構
interface ReactiveEffect {
(): any
isEffect: true
active: boolean
raw: Function // 具體執行的函數
deps: Array<Dep>
computed?: boolean
scheduler?: (run: Function) => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
onStop?: () => void
}
複製代碼
targetMap
相似 {target -> key -> dep}
的一個Map結構,用於緩存全部響應式對象和依賴收集。app
export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<string | symbol, Dep>
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
複製代碼
reactive
函數執行,會將傳入的target對象經過Proxy
包裝,攔截它的get
,set
等,並將代理的target緩存到targetMap
,targetMap.set(target, new Map())
。
代理的get
的時候會調用一個track
函數,而set
會調用一個triger
函數。分別對應依賴收集和觸發更新。
// Proxy get 簡化
function get(target: any, key: string | symbol, receiver: any) {
// 經過key拿到原始值res
const res = Reflect.get(target, key, receiver)
// 過濾不須要代理的狀況
// ...
// 依賴收集
track(target, OperationTypes.GET, key)
// 若是取到的值是個對象,將對象再代理包裝一下
// Proxy只能代理對象第一層級
return isObject(res) ? reactive(res) : res
}
// Proxy set 簡化
function set( target: any, key: string | symbol, value: any, receiver: any ): boolean {
// 一些不須要代理設置的場景
// ...
// 設置原始對象的值
const result = Reflect.set(target, key, value, receiver)
// 避免重複trigger的邏輯
// ...
// 觸發通知更新
trigger(target, '更新的類型, 新增key或更新key', key)
return result
}
複製代碼
組件在render
階段,視圖會讀取數據對象上的值進行渲染,此時便觸發了Proxy
的get
,由此觸發對應的track
函數,記錄下了對應的ReactiveEffect
,也就是常說的依賴收集。 ReactiveEffect
其實就能夠看做是組件的更新(mount是特殊的update),數據的變動觸發trigger
,trigger
遍歷調用track
收集的對應的數據的ReactiveEffect
,也就是對應有關聯的組件的更新。
trigger
觸發的組件的更新,在render
階段又觸發了新一輪的track
依賴收集,更新依賴。
// 簡化的 track
function track( target: any, type: OperationTypes, key?: string | symbol ) {
// 只有在依賴收集階段才進行依賴收集
// 除了render,其餘場景也可能會觸發Proxy的get,但不須要進行依賴收集
// activeReactiveEffectStack棧頂包裝了當前render的組件的mount和update的邏輯
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
// 若是effect爲空,說明當前不在render階段
if (effect) {
// ...
// =====>初始化對應{target -> key -> dep}的結構
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()))
}
// <=====初始化對應{target -> key -> dep}的結構
// 依賴列表裏若是沒有,add
if (!dep.has(effect)) {
// 這裏將effect做爲依賴,緩存到依賴列表
dep.add(effect)
effect.deps.push(dep)
}
}
}
// 簡化的trigger
function trigger( target: any, type: OperationTypes, key?: string | symbol, extraInfo?: any ) {
// 獲取對應target在track過程當中緩存的依賴
const depsMap = targetMap.get(target)
const effects: Set<ReactiveEffect> = new Set()
// 省略分類邏輯
depsMap.forEach(dep => {
// 將effect分類過濾添加到effects
})
const run = (effect: ReactiveEffect) => {
// 有個異步調度的過程,nextTick
scheduleRun(effect, target, type, key, extraInfo)
}
effects.forEach(run)
}
複製代碼
大體流程:
如今的代碼只有新特性的實現,並且ES6+TS的組合可讀性大大提升,編輯器支持也很好,因此相對會好讀不少。這裏只是簡單的理了一下vue 3.0 reactive的總體流程,細節還有不少地方值得學習,繼續加油。
/** PS:咱們團隊還缺人,有想換工做的小夥伴能夠發簡歷給我哦, base深圳,funkyfang(艾特)webank.com。 */
複製代碼