這篇文章主要介紹“如何用Vue實現一個渲染引擎”,在日常操作中,相信很多人在如何用Vue實現一個渲染引擎問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何用Vue實現一個渲染引擎”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
當我們得到 render 函數之后,接下來就該進入到真正的掛載階段了:
掛載 -> 實例化渲染 Watcher -> 執行 updateComponent 方法 -> 執行 render 函數生成 VNode -> 執行 patch 進行首次渲染 -> 遞歸遍歷 VNode 創建各個節點并處理節點上的普通屬性和指令 -> 如果節點是自定義組件則創建組件實例 -> 進行組件的初始化、掛載 -> 最終所有 VNode 變成真實的 DOM 節點并替換掉頁面上的模版內容 -> 完成初始渲染
所以,本篇文章目標就是實現上面描述的整個過成,完成初始渲染。整個過程中涉及如下知識點:
render helper
VNode
patch 初始渲染
指令(v-model、v-bind、v-on)的處理
實例化子組件
插槽的處理
接下來就正式進入代碼實現過程,一步步實現上述所有內容,完成頁面的初始渲染。
/src/compiler/index.js
/** * 編譯器 */ export default function mount(vm) { if (!vm.$options.render) { // 沒有提供 render 選項,則編譯生成 render 函數 // ... } mountComponent(vm) } 復制代碼
/src/compiler/mountComponent.js
/** * @param {*} vm Vue 實例 */ export default function mountComponent(vm) { // 更新組件的的函數 const updateComponent = () => { vm._update(vm._render()) } // 實例化一個渲染 Watcher,當響應式數據更新時,這個更新函數會被執行 new Watcher(updateComponent) } 復制代碼
/src/compiler/mountComponent.js
/** * 負責執行 vm.$options.render 函數 */ Vue.prototype._render = function () { // 給 render 函數綁定 this 上下文為 Vue 實例 return this.$options.render.apply(this) } 復制代碼
/src/compiler/renderHelper.js
/** * 在 Vue 實例上安裝運行時的渲染幫助函數,比如 _c、_v,這些函數會生成 Vnode * @param {VueContructor} target Vue 實例 */ export default function renderHelper(target) { target._c = createElement target._v = createTextNode } 復制代碼
/src/compiler/renderHelper.js
/** * 根據標簽信息創建 Vnode * @param {string} tag 標簽名 * @param {Map} attr 標簽的屬性 Map 對象 * @param {Array<Render>} children 所有的子節點的渲染函數 */ function createElement(tag, attr, children) { return VNode(tag, attr, children, this) } 復制代碼
/src/compiler/renderHelper.js
/** * 生成文本節點的 VNode * @param {*} textAst 文本節點的 AST 對象 */ function createTextNode(textAst) { return VNode(null, null, null, this, textAst) } 復制代碼
/src/compiler/vnode.js
/** * VNode * @param {*} tag 標簽名 * @param {*} attr 屬性 Map 對象 * @param {*} children 子節點組成的 VNode * @param {*} text 文本節點的 ast 對象 * @param {*} context Vue 實例 * @returns VNode */ export default function VNode(tag, attr, children, context, text = null) { return { // 標簽 tag, // 屬性 Map 對象 attr, // 父節點 parent: null, // 子節點組成的 Vnode 數組 children, // 文本節點的 Ast 對象 text, // Vnode 的真實節點 elm: null, // Vue 實例 context } } 復制代碼
/src/compiler/mountComponent.js
Vue.prototype._update = function (vnode) { // 老的 VNode const prevVNode = this._vnode // 新的 VNode this._vnode = vnode if (!prevVNode) { // 老的 VNode 不存在,則說明時首次渲染根組件 this.$el = this.__patch__(this.$el, vnode) } else { // 后續更新組件或者首次渲染子組件,都會走這里 this.$el = this.__patch__(prevVNode, vnode) } } 復制代碼
/src/index.js
/** * 初始化配置對象 * @param {*} options */ Vue.prototype._init = function (options) { // ... initData(this) // 安裝運行時的渲染工具函數 renderHelper(this) // 在實例上安裝 patch 函數 this.__patch__ = patch // 如果存在 el 配置項,則調用 $mount 方法編譯模版 if (this.$options.el) { this.$mount() } } 復制代碼
/src/compiler/patch.js
/** * 初始渲染和后續更新的入口 * @param {VNode} oldVnode 老的 VNode * @param {VNode} vnode 新的 VNode * @returns VNode 的真實 DOM 節點 */ export default function patch(oldVnode, vnode) { if (oldVnode && !vnode) { // 老節點存在,新節點不存在,則銷毀組件 return } if (!oldVnode) { // oldVnode 不存在,說明是子組件首次渲染 createElm(vnode) } else { if (oldVnode.nodeType) { // 真實節點,則表示首次渲染根組件 // 父節點,即 body const parent = oldVnode.parentNode // 參考節點,即老的 vnode 的下一個節點 —— script,新節點要插在 script 的前面 const referNode = oldVnode.nextSibling // 創建元素 createElm(vnode, parent, referNode) // 移除老的 vnode parent.removeChild(oldVnode) } else { console.log('update') } } return vnode.elm } 復制代碼
/src/compiler/patch.js
/** * 創建元素 * @param {*} vnode VNode * @param {*} parent VNode 的父節點,真實節點 * @returns */ function createElm(vnode, parent, referNode) { // 記錄節點的父節點 vnode.parent = parent // 創建自定義組件,如果是非組件,則會繼續后面的流程 if (createComponent(vnode)) return const { attr, children, text } = vnode if (text) { // 文本節點 // 創建文本節點,并插入到父節點內 vnode.elm = createTextNode(vnode) } else { // 元素節點 // 創建元素,在 vnode 上記錄對應的 dom 節點 vnode.elm = document.createElement(vnode.tag) // 給元素設置屬性 setAttribute(attr, vnode) // 遞歸創建子節點 for (let i = 0, len = children.length; i < len; i++) { createElm(children[i], vnode.elm) } } // 如果存在 parent,則將創建的節點插入到父節點內 if (parent) { const elm = vnode.elm if (referNode) { parent.insertBefore(elm, referNode) } else { parent.appendChild(elm) } } }
到此,關于“如何用Vue實現一個渲染引擎”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。