如何用Vue实现一个渲染引擎

这篇文章主要介绍“如何用Vue实现一个渲染引擎”,在日常操作中,相信很多人在如何用Vue实现一个渲染引擎问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何用Vue实现一个渲染引擎”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

成都创新互联长期为上千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为阎良企业提供专业的网站建设、成都做网站,阎良网站改版等技术服务。拥有10多年丰富建站经验和众多成功案例,为您定制开发。

前言

当我们得到 render 函数之后,接下来就该进入到真正的挂载阶段了:

挂载 -> 实例化渲染 Watcher -> 执行 updateComponent 方法 -> 执行 render 函数生成 VNode -> 执行 patch 进行首次渲染 -> 递归遍历 VNode 创建各个节点并处理节点上的普通属性和指令 -> 如果节点是自定义组件则创建组件实例 -> 进行组件的初始化、挂载 -> 最终所有 VNode 变成真实的 DOM 节点并替换掉页面上的模版内容 -> 完成初始渲染

目标

所以,本篇文章目标就是实现上面描述的整个过成,完成初始渲染。整个过程中涉及如下知识点:

  • render helper

  • VNode

  • patch 初始渲染

  • 指令(v-model、v-bind、v-on)的处理

  • 实例化子组件

  • 插槽的处理

实现

接下来就正式进入代码实现过程,一步步实现上述所有内容,完成页面的初始渲染。

mount

/src/compiler/index.js

/**  * 编译器  */ export default function mount(vm) {   if (!vm.$options.render) { // 没有提供 render 选项,则编译生成 render 函数     // ...   }   mountComponent(vm) } 复制代码

mountComponent

/src/compiler/mountComponent.js

/**  * @param {*} vm Vue 实例  */ export default function mountComponent(vm) {   // 更新组件的的函数   const updateComponent = () => {     vm._update(vm._render())   }   // 实例化一个渲染 Watcher,当响应式数据更新时,这个更新函数会被执行   new Watcher(updateComponent) } 复制代码

vm._render

/src/compiler/mountComponent.js

/**  * 负责执行 vm.$options.render 函数  */ Vue.prototype._render = function () {   // 给 render 函数绑定 this 上下文为 Vue 实例   return this.$options.render.apply(this) } 复制代码

render helper

/src/compiler/renderHelper.js

/**  * 在 Vue 实例上安装运行时的渲染帮助函数,比如 _c、_v,这些函数会生成 Vnode  * @param {VueContructor} target Vue 实例  */ export default function renderHelper(target) {   target._c = createElement   target._v = createTextNode } 复制代码

createElement

/src/compiler/renderHelper.js

/**  * 根据标签信息创建 Vnode  * @param {string} tag 标签名   * @param {Map} attr 标签的属性 Map 对象  * @param {Array} children 所有的子节点的渲染函数  */ function createElement(tag, attr, children) {   return VNode(tag, attr, children, this) } 复制代码

createTextNode

/src/compiler/renderHelper.js

/**  * 生成文本节点的 VNode  * @param {*} textAst 文本节点的 AST 对象  */ function createTextNode(textAst) {   return VNode(null, null, null, this, textAst) } 复制代码

VNode

/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   } } 复制代码

vm._update

/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)   } } 复制代码

安装 __patch__、render helper

/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()   } } 复制代码

patch

/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 } 复制代码

createElm

/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实现一个渲染引擎”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!


名称栏目:如何用Vue实现一个渲染引擎
浏览路径:http://scyanting.com/article/gshsee.html