这篇文章主要介绍“如何用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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。