彩蛋來了 寫在前面,最近打算學習vue3.0 相關知識,本着學習一個東西,最好方法就是模仿寫一個,因此本身動手寫了一個簡化版vue3.0,本身稱做mini-vue3.0 感受對vue3.0 或者 vue2.x核心原理的理解有很大幫助,因此分享出來。mini-vue3.0主要包括:模板編譯、響應式、組件渲染過程等, 倉庫地址mini-vue3.0,歡迎starhtml
本文簡單介紹vue3.0 組件的渲染過程,爲了更好說明組件渲染原理,本文會結合一個簡單的例子來講明整個過程。vue
簡單一點渲染過程圖node
複雜一點渲染過程圖react
設掛載點爲git
<div id="app"></div>
複製代碼
根組件定義爲github
const rootComponent = {
template: `<div class="parent"> <div :class="data.class">組件渲染內容</div> </div>`,
setup() {
// reactive 做用是設置數據響應式
const data = reactive({
class: 'demo'
})
return {
data
}
}
}
複製代碼
首先建立App全局上下文,建立的上下文以下:app
const appContext = {
mixins: [], // 存儲全局mixins
components: {}, // 存儲全局組件
directives: {}, // 存儲全局指令
}
複製代碼
const rootVnode = {
type: rootComponent, // type的值可爲字符串例如:div 或者 組件options對象
props: {},
children: {},
component: null, // 組件實例
appContext: appContext // 全局上下文,
}
複製代碼
appContext: 全局上下文中的全局組件、指令,會在render函數執行時候,生成組件VNode時,用於解析全局組件、指令 type: 若是是普通DOM元素,則爲字符串;若是爲組件節點則爲組件定義對象。在咱們例子,爲一個組件定義對象函數
根據組件VNode 初始化組件實例instance學習
const instance = {
vnode: rootVnode,// 組件vnode
parent: null,// 父組件實例,本文例子爲null
appContext, // 全局上下文
type: rootVnode.type, // 節點類型
subTree: null, // 組件內渲染VNode樹
render: null,
proxy: null,
data: {},
props: {},
setupState: {},
// 繼承全局組件和指令
components: Object.create(appContext.components),
directives: Object.create(appContext.directives),
ctx: { _: instance }
}
複製代碼
ctx: 這個ctx 屬性,是爲了渲染模板的時候,將instance自身做爲執行上下文ui
// 設置模板渲染render函數
const Component = instance.type // 組件options
if (!Component.render && Component.template && compile) {
// 編譯模板爲render函數
Component.render = compile(Component.template)
}
if (!Component.render) {
throw Error('請檢查模板是否正確')
}
instance.render = Component.render
// 設置render 函數調用時的渲染上下文
instance.proxy = new Proxy(instance.ctx, {
get({ _: instance }, key) {
const { setupState } = instance
// setupState 優先
if (setupState[key] && hasOwn(setupState, key)) {
return setupState[key]
}
},
set({ _: instance }, key, val) {
const { setupState } = instance
if (setupState[key] && hasOwn(setupState, key)) {
setupState[key] = val
}
},
})
const { setup } = Component
// 調用setup函數
if (setup) {
const setupResult = setup()
// 這裏設置響應式
instance.setupState = reactive(setupResult)
}
複製代碼
代碼中compile 爲編譯函數,具體實現原理模板編譯原理
根據本文的例子,獲得render、setupState:
// _c 爲建立VNode
instance.render = function () { return _c('div',
{class: "parent"},
[_c('div',
{class: this.data.class},
[_c('text', {value: '組件渲染內容'})]
)]
)
}
instance.setupState = {
dataProxy: {
class: 'demo'
}
}
複製代碼
// 渲染組件 effect 爲響應式相關函數,用於依賴收集,而且設置了組件update的更新函數
// effect 等價於vue2.x 中的 Watcher
instance.update = effect(function componentEffect() {
// 渲染組件模板,獲得組件子樹VNode,同時完成依賴收集,instance.proxy 其實代理就是組件自生component
const subTree = (instance.subTree = instance.render.call(instance.proxy, instance.proxy))
// 根據組件子樹VNode ,渲染組件內容
mountElement(subTree, container, anchor, instance)
rootVnode.el = subTree.el
instance.isMounted = true
})
// 根據VNode 建立具體 Dom元素
// container 父Dom 元素,在咱們例子中爲 id="app" 的元素
const mountElement = (vnode, container, anchor, parentComponent) => {
const { type, props, children } = vnode
let el
// 文本節點特殊處理
if (type === 'text') {
el = vnode.el = document.createTextNode(props.value)
} else {
el = vnode.el = document.createElement(type)
// 遞歸渲染掛載子樹
if (children) {
mountChildren(vnode.children, el, null, parentComponent)
}
// 這裏的 props 表示是Dom元素上的props
if (props) {
for (const key in props) {
el.setAttribute(key, props[key])
}
}
}
// 插入DOM中
document.appendChild(container, el, anchor)
}
複製代碼
本文例子中根組件 獲得的subTree以下:
{
"type": "div",
"props": {
"class": "parent"
},
"children": [{
"type": "div",
"props": {
"class": "before"
},
"children": [{
"type": "text",
"props": {
"value": "組件渲染內容"
},
"component": null,
"appContext": null
}],
"component": null,
"appContext": null
}],
"component": null,
"appContext": null
}
複製代碼