手写 Vue2 系列 之 初始渲染

当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注点赞收藏评论

新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn

文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。

前言

上一篇文章 手写 Vue2 系列 之 编译器 中完成了从模版字符串到 render 函数的工作。当我们得到 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<Render>} 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 节点
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值