写点什么

【Vue2.x 源码学习】第十九篇 - 根据 vnode 渲染真实节点

用户头像
Brave
关注
发布于: 2 小时前
【Vue2.x 源码学习】第十九篇 - 根据 vnode 渲染真实节点

一,前言


上篇,根据 render 函数,生成 vnode,主要涉及一下几点:

  • 封装 vm._render 返回虚拟节点

  • _s,_v,_c 的实现


本篇,根据 vnode 虚拟节点渲染真实节点


二,

1,前情回顾


mountComponent 方法:将组件挂载到 vm.$el 上

mountComponent 中,调用 vm 实例方法 _render 返回 vnode

_render 方法:在 Vue 初始化时,通过 renderMixin 被扩展到 Vue 原型上

在 _render 中执行前边生成的 render 函数(render 的生成:1,拼接 code;2,with + new Function;)

render 在执行过程中,会调用_s,_v,_c 方法,最终返回了 vnode

// src/lifecycle.jsexport function mountComponent(vm) {  vm._render();	// 调用 render 函数,内部触发_s,_v,_c...}

// src/render.jsexport function renderMixin(Vue) { Vue.prototype._render = function () { const vm = this; let { render } = vm.$options; let vnode = render.call(vm); // 内部会调用_c,_v,_s方法 return vnode; // 返回虚拟节点 } Vue.prototype._c = function () {/* 创建元素虚拟节点 */} Vue.prototype._v = function () {/* 创建文本虚拟节点 */} Vue.prototype._s = function () {/* JSON.stringify */}}
复制代码


vm._render 执行完成后,就已经得到了虚拟节点 vnode

接下来,就需要将 vnode 更新到页面上了


需要 vm._update 方法:专门负责将虚拟节点更新到页面上

2,vm._update 方法

// src/lifecycle.js
export function mountComponent(vm) { // vm._render():调用 render 方法 // vm._update:将虚拟节点更新到页面上 vm._update(vm._render()); }
复制代码


在 Vue 原型上扩展 _update 方法:

export function mountComponent(vm) {  vm._update(vm._render());  }
export function lifeCycleMixin(Vue){ Vue.prototype._update = function (vnode) { console.log("_update-vnode", vnode) }}
复制代码

并在 Vue 初始化时完成 Mixin:

import { initMixin } from "./init";import { lifeCycleMixin } from "./lifecycle";import { renderMixin } from "./render";
function Vue(options){ this._init(options);}
initMixin(Vue)renderMixin(Vue)lifeCycleMixin(Vue) // 在 Vue 原型上扩展 _update 方法
export default Vue;
复制代码

测试:

<body>  <div id="app">    <li>{{name}}</li>    <li>{{age}}</li>  </div>  <script src="./vue.js"></script>  <script>    let vm = new Vue({      el: '#app',      data() {        return { name:  "Brave" , age : 123}      }    });   </script></body>
复制代码

vm._render 方法执行完成后,返回 vnode

继续进入 vm._update,查看打印 vnode 结果:

TODO:_update 为什么选择在 Vue 原型上进行扩展?

3,patch 方法


vnode 是一个描述了节点关系的对象

根据虚拟节点渲染出真实节点,就需要将 vnode 对象递归进行渲染(先序深度遍历创建节点)

备注:

由于 vue 的更新特性是组件级别的,因此合理进行组件化拆分,能够有效避免递归产生的性能问题


patch 方法:将虚拟节点转为真实节点后插入到元素中

patch 方法所属 vdom 模块,创建 src/vdom/patch.js

备注:

后续的 diff 算法也是在 patch 方法中进行

// src/vdom/patch.js
/** * 将虚拟节点转为真实节点后插入到元素中 * @param {*} el 当前真实元素 id#app * @param {*} vnode 虚拟节点 * @returns 新的真实元素 */export function patch(el, vnode) { console.log(el, vnode) // 根据虚拟节点创造真实节点,替换为真实元素并返回}
复制代码


在 vm._update 中使用 patch 方法:

// src/lifeCycle.js#lifeCycleMixin
export function lifeCycleMixin(Vue){ Vue.prototype._update = function (vnode) { const vm = this; // 传入当前真实元素vm.$el,虚拟节点vnode,返回新的真实元素 vm.$el = patch(vm.$el, vnode); }}
复制代码

测试:


Vue 文档中的说明:

创建一个新的 $el,并且使用它来替换掉原来的 el


所以,后面继续要做的事:

  1. 根据右侧 vnode 创建真实节点

  2. 使用真实节点替换掉左侧老节点 id#app


备注:到目前为止,只有一个根节点,还没有涉及到组件级别的更新


如何替换节点?

replace 替换:需要找到父亲,并指定用谁替换谁,使用不方便

Vue 的实现:找到老节点,将新节点放到老节点的后面,删除新节点

优点:能够确保更新后节点在文档中的顺序不变

4,根据虚拟节点创建真实节点

// src/vdom/patch.js
// 将虚拟节点转为真实节点后插入到元素中export function patch(el, vnode) { // 1,根据虚拟节点创造真实节点 const elm = createElm(vnode); // 2,使用真实节点替换老节点 return elm;}
复制代码


function createElm(vnode) {  let el;  let{tag, data, children, text, vm} = vnode;  // 通过 tag 判断当前节点是元素 or 文本,判断逻辑:文本 tag 是 undefined  if(typeof tag === 'string'){    el = document.createElement(tag) 		// 创建元素的真实节点    // 继续处理元素的儿子:递归创建真实节点并添加到对应的父亲上    children.forEach(child => { // 若不存在儿子,children为空数组,循环终止      el.appendChild(createElm(child))    });  } else {    el = document.createTextNode(text)  // 创建文本的真实节点  }  return el;}
复制代码


创建真实节点和虚拟节点的映射关系


三,结尾


又成功水了一篇,还剩 2 篇


本篇,根据 vnode 虚拟节点渲染真实节点,主要涉及一下几点:


下一篇,根据 vnode 虚拟节点渲染真实节点

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【Vue2.x 源码学习】第十九篇 - 根据 vnode 渲染真实节点