深圳定制建站鄭州seo多少錢
文章目錄
- os
- 準(zhǔn)備
- 組件如何被掛載到頁(yè)面上
- 第一步 createApp 做了哪些工作?
- ensureRenderer
- baseCreateRenderer
- createAppAPI
- mount
- render
- patch
- processComponent
- processElement
- 總結(jié)
os
學(xué)習(xí)一下vue3 源碼,順便記錄分享下
使用vitest 插件調(diào)試源碼 輔助閱讀
準(zhǔn)備
去 github 下載 vue3源碼 最新倉(cāng)庫(kù)名 為 core-main 使用 版本 為3.3.4
安裝好依賴
npm i pnpm -g pnpm install
vscode 準(zhǔn)備兩個(gè)插件 方便代碼調(diào)試
安裝后會(huì)出現(xiàn)調(diào)試icon 未生效 可以重啟vscode 。
代碼打上 斷點(diǎn), 開啟debug 調(diào)試
1 跳到下一個(gè)方法體
2 逐步執(zhí)行
3 回退到上一步
4 重新執(zhí)行
最后一個(gè)按鈕就是 結(jié)束執(zhí)行
組件如何被掛載到頁(yè)面上
createApp(App).mount('#app')
第一步 createApp 做了哪些工作?
先看下入?yún)⒑统鰠?/p>
export type CreateAppFunction<HostElement> = (rootComponent: Component,rootProps?: Data | null
) => App<HostElement> 入?yún)?#xff1a; rootComponent 需要渲染的組件 App 也就是我們編寫的 App.vue 文件rootProps 傳入根實(shí)例 的 props 最后會(huì)被 掛在 app _props 上
出參 : 返回app 實(shí)例對(duì)象
// packages/runtime-dom/src/index.ts
export const createApp = ((...args) => {// 調(diào)用 ensureRender 生成render 對(duì)象 const render = ensureRenderer()// 再調(diào)用 render中 createApp 方法 來(lái)生成 app實(shí)例 const app = render.createApp(...args)···· 下面先省略return app
})
ensureRenderer
// packages/runtime-dom/src/renderer.ts
// 實(shí)際調(diào)用的是 createRenderer
function ensureRenderer() {
/*
大致意思是 判斷renderer實(shí)例是否存在,有就直接返回
沒(méi)有執(zhí)行 createRender 方法并 賦值 renderer 再返回
這里返回的 renderer 對(duì)象,可以認(rèn)為是一個(gè)跨平臺(tái)的渲染器對(duì)象,
針對(duì)不同的平臺(tái),會(huì)創(chuàng)建出不同的 renderer 對(duì)象,
上述是創(chuàng)建瀏覽器環(huán)境的 renderer 對(duì)象,對(duì)于服務(wù)端渲染的場(chǎng)景,
則會(huì)創(chuàng)建 server render 的 renderer*/return (renderer ||(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)))
}// 實(shí)際調(diào)用 baseCreateRenderer
function createRenderer<HostNode = RendererNode,HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {return baseCreateRenderer<HostNode, HostElement>(options)
}
baseCreateRenderer
有兩種模式 hydration 模式是 服務(wù)端渲染的 我們只考慮 no-hydration 瀏覽器渲染
no-hydration 下
入?yún)?#xff1a; options 初始化 渲染的參數(shù)
出參 :render export interface Renderer<HostElement = RendererElement> {render: RootRenderFunction<HostElement>createApp: CreateAppFunction<HostElement>
}
具體偽代碼 實(shí)現(xiàn)
// packages/runtime-core/src/renderer.ts
export function createRenderer(options) {// ...// 這里不介紹 hydrate 模式return {render,hydrate, // no-hydration 為空createApp: createAppAPI(render, hydrate),}
}
createAppAPI
// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI(render, hydrate) {// createApp createApp 方法接收的兩個(gè)參數(shù):根組件的對(duì)象和 propreturn function createApp(rootComponent, rootProps = null) {// 。。。 省略const app = {// ... 省略很多不需要在這里介紹的屬性_component: rootComponent,_props: rootProps,mount(rootContainer, isHydrate, isSVG) {// ...}}return app}
}
Vue 3 初始化根組件的核心方法,也就是入口文件 createApp 真正執(zhí)行的內(nèi)容就是這里的 createAppAPI 函數(shù)中的 createApp 函數(shù),該函數(shù)接收了 組件作為根組件 rootComponent,返回了一個(gè)包含 mount 方法的 app 對(duì)象,再看看 mount 具體實(shí)現(xiàn)
mount
// packages/runtime-core/src/apiCreateApp.ts
mount(rootContainer, isHydrate, isSVG) {if (!isMounted) {// ... 省略部分不重要的代碼// 1. 創(chuàng)建根組件的 vnodeconst vnode = createVNode(rootComponent,rootProps)// 2. 渲染根組件 這里render方法 其實(shí)是baseCreateRenderer // 返回的render對(duì)象帶的 render方法 // 作用就是 將 vnode 渲染成真實(shí)domrender(vnode, rootContainer, isSVG)isMounted = true}
}
render
// packages/runtime-core/src/renderer.ts
const render: RootRenderFunction = (vnode, container, isSVG) => {// console.log('render-----');//第一個(gè) 入?yún)?沒(méi)傳 代表 需要卸載 if (vnode == null) {if (container._vnode) {unmount(container._vnode, null, null, true)}} else {// 否則走 掛載 或更新 操作patch(container._vnode || null, vnode, container, null, null, null, isSVG)}flushPreFlushCbs()flushPostFlushCbs()container._vnode = vnode}
// patch 所有vnode diff 比對(duì) 更新 轉(zhuǎn)化新dom 操作全在里面
patch
const patch: PatchFn = (n1, // 需要 對(duì)比的 舊 vnoden2, // 新生成的 vnode container, // 最后生成的元素 需要掛載到的 目標(biāo)組件元素anchor = null, // 掛載的參考元素;parentComponent = null, // 父組件parentSuspense = null,isSVG = false,slotScopeIds = null,optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren) => {//n1 n2 完全一致 就 直接返回 不做更新 或 掛載if (n1 === n2) {return}// patching & not same type, unmount old tree 新舊 vnode 類型 不一樣 直接 卸載 n1 if (n1 && !isSameVNodeType(n1, n2)) {anchor = getNextHostNode(n1)unmount(n1, parentComponent, parentSuspense, true)n1 = null}if (n2.patchFlag === PatchFlags.BAIL) {optimized = falsen2.dynamicChildren = null}// shapeFlag 判斷vnode 實(shí)例是什么類型 有的是元素類型 函數(shù)類型 組件類型等const { type, ref, shapeFlag } = n2switch (type) {//文本節(jié)點(diǎn)case Text:processText(n1, n2, container, anchor)break// 注釋節(jié)點(diǎn)case Comment:processCommentNode(n1, n2, container, anchor)breakcase Static:if (n1 == null) {mountStaticNode(n2, container, anchor, isSVG)} else if (__DEV__) {patchStaticNode(n1, n2, container, isSVG)}breakcase Fragment:// 處理 template 的虛擬標(biāo)簽processFragment(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)breakdefault:// 其它類型//ShapeFlags 是一個(gè)二進(jìn)制左移操作符生成的對(duì)象if (shapeFlag & ShapeFlags.ELEMENT) {// 這里走的是 組件內(nèi)部元素普通dom的比對(duì)更新掛載邏輯processElement(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)} else if (shapeFlag & ShapeFlags.COMPONENT) {// 這里是 組件對(duì)比 component 邏輯 processComponent(n1,n2,container,anchor,parentComponent,parentSuspense,isSVG,slotScopeIds,optimized)} 。。。 // 其它省略// set refif (ref != null && parentComponent) {/*通過(guò) ref 參數(shù)獲取組件的引用對(duì)象。通過(guò) n1 參數(shù)獲取前一個(gè) VNode 的引用對(duì)象(如果存在)。通過(guò) n2 參數(shù)獲取當(dāng)前 VNode 的引用對(duì)象(如果存在)。如果前一個(gè) VNode 的引用對(duì)象存在(即 n1.ref 存在),則將其置為 null,解除對(duì)前 一個(gè)組件引用的綁定。如果當(dāng)前 VNode 的引用對(duì)象存在(即 n2.ref 存在),則將其綁定到組件的引用上。如果當(dāng)前 VNode 不存在(即 !n2),則將組件的引用對(duì)象置為 null*/setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)}}
初始化掛載 會(huì)進(jìn)入到 processComponent方法
processComponent
// packages/runtime-core/src/renderer.ts
function processComponent(n1, n2, container, parentComponent) {// 如果 n1 沒(méi)有值的話,那么就是 mountif (!n1) {// 初始化 componentmountComponent(n2, container, parentComponent);} else {updateComponent(n1, n2, container);}
}// packages/runtime-core/src/renderer.ts
function mountComponent(initialVNode, container, parentComponent) {// 1. 先創(chuàng)建一個(gè) component instance const instance = (initialVNode.component = createComponentInstance(initialVNode,parentComponent));// 2. 初始化 instance 上的 props, slots, 執(zhí)行組件的 setup 函數(shù)...setupComponent(instance);// 3. 設(shè)置并運(yùn)行帶副作用的渲染函數(shù)setupRenderEffect(instance, initialVNode, container);
}// packages/runtime-core/src/component.ts
function createComponentInstance(vnode: VNode,parent: ComponentInternalInstance | null,suspense: SuspenseBoundary | null
) {const type = vnode.type as ConcreteComponent// inherit parent app context - or - if root, adopt from root vnodeconst appContext =(parent ? parent.appContext : vnode.appContext) || emptyAppContextconst instance: ComponentInternalInstance = {uid: uid++,vnode,type,parent,appContext,root: null!, // to be immediately setnext: null,subTree: null!, // will be set synchronously right after creationeffect: null!,update: null!, // will be set synchronously right after creationscope: new EffectScope(true /* detached */),render: null,proxy: null,//。。。 省略 屬性}if (__DEV__) {instance.ctx = createDevRenderContext(instance)} else {instance.ctx = { _: instance }}instance.root = parent ? parent.root : instanceinstance.emit = emit.bind(null, instance)// apply custom element special handlingif (vnode.ce) {vnode.ce(instance)}return instance
}// packages/runtime-core/src/component.ts
export function setupComponent(instance) {// 1. 處理 props// 取出存在 vnode 里面的 props const { props, children } = instance.vnode;initProps(instance, props);// 2. 處理 slotsinitSlots(instance, children);// 3. 調(diào)用 setup 并處理 setupResultsetupStatefulComponent(instance);
}// packages/runtime-core/src/renderer.ts
/*
componentUpdateFn 這個(gè)函數(shù),
核心是調(diào)用了 renderComponentRoot 來(lái)生成 subTree,
然后再把 subTree 掛載到 container 中
*/
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {function componentUpdateFn() {if (!instance.isMounted) {// 渲染子樹的 vnodeconst subTree = (instance.subTree = renderComponentRoot(instance))// 掛載子樹 vnode 到 container 中 // 會(huì)重新進(jìn)入 patch 方法 會(huì)走到 processElement 方法中patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)// 把渲染生成的子樹根 DOM 節(jié)點(diǎn)存儲(chǔ)到 el 屬性上initialVNode.el = subTree.elinstance.isMounted = true}else {// 更新相關(guān),后面介紹}}// 創(chuàng)建副作用渲染函數(shù)instance.update = effect(componentUpdateFn, prodEffectOptions)
}/*
返回 vnode
*/
function renderComponentRoot(instance: ComponentInternalInstance
): VNode {const {type: Component,vnode,proxy,withProxy,props,propsOptions: [propsOptions],slots,attrs,emit,render,renderCache,data,setupState,ctx,inheritAttrs} = instanceconst proxyToUse = withProxy || proxy// 省略一部分邏輯判斷 normalizeVNode
/*render 方法 其實(shí)是調(diào)用instance.render 方法 就是在 初始化instance 方法 中 將 template 模版 編譯成 render 方法 用于 生成 vnode
*/result = normalizeVNode(render!.call(proxyToUse,proxyToUse!,renderCache,props,setupState,data,ctx))return result
}
processElement
// packages/runtime-core/src/renderer.ts
function processElement(n1, n2, container, anchor, parentComponent) {if (!n1) {// 掛載元素節(jié)點(diǎn)mountElement(n2, container, anchor);} else {// 更新元素節(jié)點(diǎn)updateElement(n1, n2, container, anchor, parentComponent);}
}// packages/runtime-core/src/renderer.ts
const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {let elconst { type, props, shapeFlag, transition, patchFlag, dirs } = vnode// ...// 根據(jù) vnode 創(chuàng)建 DOM 節(jié)點(diǎn)el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is)if (props) {// 處理 props 屬性for (const key in props) {if (!isReservedProp(key)) {hostPatchProp(el, key, null, props[key], isSVG)}}}// 文本節(jié)點(diǎn)處理if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {hostSetElementText(el, vnode.children)} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {// 如果節(jié)點(diǎn)是個(gè)數(shù)據(jù)類型,則遞歸子節(jié)點(diǎn)mountChildren(vnode.children, el)}// 把創(chuàng)建好的 el 元素掛載到容器中hostInsert(el, container, anchor)
}
總結(jié)
以上就完成了 組件初始化工作。下面畫了 幾個(gè)流程圖來(lái)輔助理解 。最好閱讀的時(shí)候自己 也可以畫下
安利一個(gè)好用的vscode流程圖插件
下一篇:準(zhǔn)備寫 數(shù)據(jù)代理這塊