寫在前面
最近又抽時間把 vue-next/runtime-core
的源碼陸陸續續地看完了,期間整理了很多筆記,但都是碎片化的。本來是想整理一下,寫成一篇文章分享出來的,但是感覺最終的成果物只能是一篇篇幅巨長的解析文,就算我一行一行的把源碼加上註釋,其閱讀體驗也會很差,因爲每個人讀代碼的習慣不同,思路不同。正所謂拋磚引玉,所以,我覺的寫一篇嚮導文作爲這塊磚應該是足夠了,希望可以幫助到想看源碼但覺得無從看起、無從下手的讀者。
另一方面,也算是給自己挖一個坑,因爲這篇文章中涉及到的很多內容,三言兩語肯定是說不清的,這就意味着之後必須要寫其他文章來填補這些空白。我會儘可能的將高內聚的模塊整理到一起,然後再分享出來,儘量避免陷入羅列代碼的境地,從而提高文章質量吧。
準備工作
工欲善其事,必先利其器,要看源碼,拿寫字板來看肯定是不行的(當然也不排除牛人)。你所需要的就是一個支持代碼跳轉的編輯器即可,我用的是 VSCode
,當然了,如果你用 VIM
、Sublime
也是可以的。
另外還需一些儲備知識:
- 由於是閱讀
vue-next
的代碼,並且是pre-alpha
的版本,這就要求你對之前vue
有一定的瞭解,如果是第一次接觸,我覺的閱讀源碼的意義也不是很大 -
需要熟練掌握
debug
的基礎技巧和流程,通過debug
的方式來看代碼有兩個好處- debug 過程有清晰地調用棧記錄
- 各種作用域下的變量一目瞭然
- 需要對
typescript
有一定掌握程度,最起碼給知道interface
、enum
等概念
如何閱讀
一般有三種途徑:
- 直接看
- 通過單元測試的可執行代碼
- 自己編寫的可執行代碼
這裏推薦第二種方式,因爲單元測試是官方團隊維護的,質量肯定有保證,二來單元測試一般都很簡單,同時帶有註釋,這有利於我們理解代碼。
由於 vue-next
使用 jest
進行單元測試,在 vscode
中安裝 Jest
插件即可,它支持行內 debug lens 快捷入口,方便直接對某條單元測試進行 debug
。
不過要注意配置一個自定義選項:
"jest.debugCodeLens.showWhenTestStateIn": [
"fail",
"unknown",
"pass", // 注意這裏
]
這裏的 "pass"
代表即使單元測試通過,也會在上方顯示 debug
lens,默認情況下,單元測試用例通過的話,這個 debug
lens 標識不會顯示。
模塊職能歸納
runtime-core
目錄下有多個文件,我暫且把每個文件都當做一個子模塊來看待。vue
的代碼質量還是挺好的,模塊與模塊之間的耦合性都不是特別高,正因爲如此,基本上每個模塊都有自己單獨對應的單元測試文件。
我在看的時候,基本上就是挨個看這些模塊的單元測試,然後調試過程中,會主動的進行一些代碼跳轉,去看一下具體的實現細節。下面把當前最新代碼下該文件目錄下的所有模塊的職能進行一些總結和歸納。
有一些較獨立的模塊我還沒有看完,但是不影響整體的源碼閱讀進程,日後對這些獨立模塊進行單獨研究時,纔回來補充這些 todo
就好了。
公共 api
相關
實現公共 api
的模塊均是以 apixxx
這樣的格式來命名的,如下:
-
apiApp.ts
: 有 3 個比較重要的接口需要看一下,App
、AppConfig
和AppContext
,如果對於vue2
比較熟悉的話,會很容易理解。createApp
是一個工廠方法,返回一個符合App
接口約束的對象,其內部方法會與一個符合AppContext
接口約束的對象進行交互。 -
apiCreateComponent.ts
: 這個文件內部包含多個對於createComponent
函數簽名的重載聲明,其存在目的應該是爲了幫助 ts 提供更好的類型推斷以提升開發體驗。 -
apiInject.ts
: 組件依賴注入feature
的實現邏輯,實現方式很簡單,直接與component
文件中暴露的currentInstance
變量進行交互,實現繼承、賦值、取值等邏輯 -
apiLifecycle.ts
: 新的組件聲明週期hooks
,主要看injectHook
方法就可以了,這裏的target
默認情況下指向currentInstance
,之後會在將某個回調邏輯緩存在currentInstance
實例的聲明週期回調函數數組中 -
apiOptions.ts
:其中包含對於component
如何解析options
的實現邏輯,代碼比較長同時也比較複雜,耦合性與其他幾個文件較高。但其實沒有必要直接看完它內部的全部代碼,因爲options
中每一段的解析邏輯互相之間都是獨立的,因此可以專門針對某個option
單獨去看它內部的解析邏輯,我目前只看了data
以及lifecycle
。 -
apiReactivity.ts
:就是對reactivity
包中的api
的重新導出,沒有什麼額外的東西 -
apiWatch.ts
: 暫時還沒仔細看,不過根據名字可知是和watch
相關的api
,粗略的看了一下,發現耦合性比較低,因此可以日後再看
組件相關
-
component.ts
: 主要包含如何創建一個內部組件實例的邏輯,代碼比較長,但是點進去看的話,會發現它其實是在調用其他模塊的暴露的 api,本身的邏輯還是比較簡單的。需要注意的是,這個文件會暴露一對setCurrentInstance
和getCurrentInstance
方法用來維護currentInstance
變量的指向,同時它會在別的模塊中被使用到 -
componentProxy.ts
: 聲明瞭render proxy
的實現邏輯,這個proxy
主要負責外部如何與內部組件實例進行交互,可以將它看做是一個外部組件實例 -
componentProps.ts
: 主要看resolveProps
,實現瞭如何解析各種形式的props
-
componentSlots.ts
:主要看resolveChildren
,實現瞭如何解析各種形式的children
節點 -
componentRendererUtils.ts
: 一些渲染組件的util
方法,按名字瞭解各個方法的含義即可 -
createRenderer.ts
:這個和其他文件耦合度較低,可以理解爲VNode
的渲染器,只要實現了其接口,可以在任何上下文環境中進行渲染,比如小程序、native
、canvas
或者內存環境,關於如何編寫一個renderer
,直接看runtime-test
或者runtime-dom
的代碼即可 -
directives.ts
: 指令相關的內部api
,當前的代碼版本,這部分可能很多todo
因此可以日後再回來看看
其他
-
errorHandling.ts
: 錯誤處理相關,暫時還沒仔細看 -
scheduler.ts
: 作業調度器相關,暫時還沒仔細看 -
shapeFlags.ts
: 組件本身和children
類型的枚舉聲明及常量 -
suspense.ts
:suspense
相關,暫時還沒看,對於其他文件中的suspense
的相關邏輯,我完全是按照react
中相關概念來理解的,暫時沒遇到任何障礙 -
warning.ts
: 警告相關,大部分是一些 util 方法,按名字理解其含義就好了
推薦的閱讀順序
直接說我自己的閱讀順序,我認爲還是比較符合認知習慣的:
- 先看
vnode.ts
和h.ts
等關於vdom
的代碼瞭解一下新的VNode
的數據結構 - 然後再看
apiApp.ts
,看掛載過程是怎樣把VNode
和渲染上下文關聯起來的,這個過程中自然會涉及到各種apixxx.ts
中的內容,挨個看就好了 - 由於之前看的都是公共
api
,需要了解實現細節的話,要進一步看component.ts
,其中主要包含內部組件實例的數據結構以及創建流程,同樣地,打斷點一行一行讀即可 - 對於一些解析、工具方法,可以放到最後再看其實現細節,打斷點的過程中沒有必要一探到底,因爲有些方法的名字已經可以很明確的說明其背後實現的邏輯是什麼了
期間會遇到 suspense
、lifecycle
之類的代碼,這類代碼也可以當做單獨的內容進行閱讀,在一開始看的時候,也可以不要太糾結於細節,當對整體流程有一個大概瞭解之後再回頭來看會清晰很多,之後我會專門整理一篇文章介紹這塊是如何實現的。
寫在最後
雖然 vue-next
的代碼現在還處在初期的階段,但是整體的閱讀體驗還是不錯的,結構清晰,可讀性也比較高,一些關鍵模塊也有註釋進行說明,唯一不足的地方在於,很多地方還是藉助 as
關鍵字來進行類型斷言,我覺得這些地方可能有更好的方式實現類型推斷吧。
關注 全棧_101,只談技術,不談人生
另:接各種規模全棧外包項目,有意者私聊