前言
vue3.x比vue2.x在很多方面都優于vue2.x,比如vue3.x的可擴展性;下面我們一起學習一下vue3.x原始碼的初始化程序
測驗代碼
<div id="app">
{{title}}
<comp></comp>
</div>
<script src="../dist/vue.global.js"></script>
<script>
// vue3里面沒有全域api了,都是一些實體的方法
// createApp() => mount() => render() =>patch(判斷一開始初始化的結果存不存在,然后有兩條路線要走) => processComponent() =>mountComponet():整個大致的流程;
// createRenderer
const { createApp } = Vue
createApp({
data() {
return {
title: '如果太晚了,就看看電視睡了吧'
}
},
})
.component('comp', {
template: '<div>comp</div>'
})
.mount('#app')
</script>
正文
打斷點,ready go

進入渲染器,創建實體,進去之后會得到應用程式的實體,沒有的話就創建一個;


進入到createAppAPI,這個方法很復雜,但是是初始化的核心方法;創建根組件實體,根組件實體就是data里面的內容,再往下走會創建應用程式的背景關系并保存狀態;

在這里可以看到,在這里面可以看到創建一個物件,這個實體里面有很多屬性以及方法;
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
往下看會看到以下的方法,這里面掛載實體方法,在vue3里面全部變成了實體方法,不存在靜態方法,也不存在全域方法,這樣的好處是按需加載,這樣寫的代碼就不會死,我們最后輸出的包也會變小,繼續往下看會發現vue3.0里面的filter方法被遺棄了,
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
return app
},
在214行打斷點看掛載程序,

查看虛擬節點,會發現和以前很多不一樣的地方,這里不一一深究了,

執行到233行的時候,這一個方法的作用是將虛擬節點掛載到真實的dom上面;

進入陳述句,會出現渲染傳入vnode,到指定容器中,這個就是用來做渲染器的,如果想重新寫一個平臺的代碼的話可以直接重寫這個代碼,這個就相當于給第三方提供了一個很好的介面,

執行patch,如果之前有虛擬dom的話,就會執行大名鼎鼎的diff演算法,沒有的話就會直接創建虛擬dom;
const render: RootRenderFunction = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode
}
繼續往下執行,執行到469行的 switch,然后執行default;
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default:
}
初始化流程的代碼會執行下面幾行代碼
else if (shapeFlag & ShapeFlags.COMPONENT) {
// 初始化走這個
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
進入陳述句,其中關鍵的步驟mountComponent(掛載組件的程序),初始化走掛載流程
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else {
// 初始化走掛載流程
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
到這里,我們梳理一下大致的流程,核心的一些流程為createApp()=>mount()=>render=>patch()=>processComponent()=>mountComponent();
進入到mountComponent里面,會執行創建組件實體;如下代碼所示;
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
查看instance,會發現里面有一些和以前不一樣的地方,其中bc、bm、bu、bum為那些生命周期函式鉤子的縮寫,這里面有個ctx,以前vue2.x里面的實體實際上是vue3.x里面的ctx;

然后執行setupComponent(instance),安裝實體,進入之后,會在代碼里面看到會對屬性、插槽做初始化,如果是狀態形組件,代碼會走setupStatefulComponent

繼續往下看,在這里面可以看到proxy,其中proxy做代理在這里面是回應式的,如果想實作回應式的話,不能在ctx里面去實作,應該考慮去用proxy實作;再往下面就是和組合式api相關的內容了,這里就不一一贅述了;
instance.accessCache = {}
// 1. create public instance / render proxy
// also mark it raw so it's never observed
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
if (__DEV__) {
exposePropsOnRenderContext(instance)
}
// 2. call setup()
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
currentInstance = instance
pauseTracking()
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
currentInstance = null
繼續往下執行,執行依賴收集,這也是回應式里面很核心的一個內容,這里就不細看了;
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
if (__DEV__) {
popWarningContext()
endMeasure(instance, `mount`)
}
}
繼續往下看,執行patch,初始化的時候沒有舊的虛擬dom;執行完patch之后,整個程式就到此結束了;左面的效果也全部出來了;

結尾
從初始化流程來看,vue3.x的原始碼和vue2.x原始碼大不相同,個人感覺相對復雜不少,很多地方都不一樣,其中比較秒的地方就是vue3.x對外提供一個渲染器,也就相當于一個介面,擴展性大幅度提高,這樣第三方就可以很巧妙的通過這個介面去搭建自己的平臺,像uniapp以前必須要從頭到尾去改vue的原始碼,但是現在uniapp可以通過這個介面來進行二次開發,可以節省很多的時間和精力,水平有限,如有不足,還請大佬多多指教,下次會補充初始化里面的組合式api的相關內容,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/382011.html
標籤:其他
下一篇:前后端互動
