Vue 中是如何解析 template 字符串为 VNode 的?

 

在接触 React 时候,我只了解到经过 babel 能够把 JSX 转成 VNode(经过调用 React.createElement 方法),可是对其具体是如何转换的却不了解。html

很明显,回答失败。经过 github 上搜索 template+vnode 的关键词,让我搜到了htm库,发现简直就是我想要的。让咱们看下用法:vue

const htm = require("htm"); function h(type, props, ...children) { return { type, props, children }; } const html = htm.bind(h); html` <div>Hello World</div> `; // 返回: { type: 'div', props: null, children: ['Hello World'] } 复制代码

htm 的大概思路是经过一个个字符遍历 template 字符串,并设置状态类型,当遇到<>表示进入元素状态,遇到="'则表示属性状态。子元素的关系经过数组的 push 和 slice 某一位来肯定。 更详细能够看看这篇文章如何解析 template 成 VNODEnode

为何要用 VNode?

我想这里应该是经过比较 VNode 和 DOM,并给出 VNode 的优点和 DOM 的不足。git

当前 Vue 和 React 都使用了 VNode,是出于什么缘由,让两大目前最火热的框架都选择使用了 VNode 呢?github

这里咱们直接看下写的比较好的文章吧. 深度剖析:如何实现一个 Virtual DOM 算法面试


了解到上面知识的大体原理后,回顾了下 React 的 JSX 写法:算法

  1. 当咱们须要遍历列表
render() {
  return ( <ul> { list.map(item => <li>item</li>) } </ul> ) } 复制代码
  1. 当咱们渲染值
render() {
  return ( <p>{{ msg }}</p> ) } 复制代码

思考了下,若是结合 ejs 等模板引擎(这些模板引擎大体的思路是结合 template+data->html->设置到 DOM 的 innerHTML),先把数据填充进去,转变成 html 字符串。npm

以后使用htm转成 VNode,再使用 Virtual Dom,使用 Virtual Dom 的 diff 和 patch,即可以实现了简单的 MVVM 体验。数组

没错,就是这么简单,废话很少说,开干吧。babel

MVVM

模板引擎

<!-- 好比咱们须要渲染数组列表: --> <ul> <% for (let item of list) { %> <li></li> <% } %> </ul> <!-- 好比咱们须要条件渲染 --> <% if (condition) { %> <span>open</span> <% } else { %> <span>close</span> <% } %> <!-- 好比咱们须要渲染数据 --> <p><%= msg %></p> 复制代码

个人思路的先处理逻辑运算如:(for,if 等), 经过正则/<%[^=]([^%]*)%>/g来匹配,并经过str += 匹配内容, 由于 exec 会含有 index 属性,因此匹配以前的 html 经过 slice 来获取,并拼接到 str。

let _str = 'let str = "";\n'; let exec; let index = 0; let content; while ((exec = REG.exec(str))) { content = str_format(str.slice(index, exec.index)); if (content) { _str += `str += '${content}';\n`; } _str += `${str_format(exec[1])}\n`; index = exec.index + exec[0].length; } // some code 复制代码

处理完逻辑的代码,经过正则/<%=([^%]*)%>/g直接对上面的字符串进行 replace 操做替换。

具体代码: template.js

html 字符串 -> VNode

这里咱们使用simple-virtual-dom库来实现虚拟 DOM 处理,咱们对上面函数 h 作一点调整。

import { el } from "simple-virtual-dom"; import htm from "htm"; function h(tagName, props, ...children) { return new el(tagName, props, children); } const html = htm.bind(h); const vnode = html([html_str]); 复制代码

这里咱们就实现了template+data -> html str -> VNode的转换。使用 VNode 库提供的 render 转成具体的 DOM 并挂载到 document 上。

可是咱们貌似尚未对事件进行处理,这里我使用了事件委托机制,也就是挂载事件到 window 对象上进行监听处理。因此这里须要对simple-virtual-dom库的 element.js 作一点小调整.

// 惟一Id let uid = 0; function Element(tagName, props, children) { // 给每一个VNode增长uid this.uid = uid++; } Element.prototype.render = function() { for (var propName in props) { var propValue = props[propName]; // 这里模仿vue的事件绑定 if (propName.startsWith("@")) { // 事件处理 const callback = (vm.$methods[propValue] || function() {}).bind(vm); delegate(window, `[dance-el-${this.uid}]`, propName.slice(1), callback); continue; } } // 添加uid属性, 为了事件代理 _.setAttr(el, "dance-el-" + this.uid, ""); }; 复制代码

这样,事件处理咱们也解决好了,哦对了,对 delegate 实现原理感兴趣的能够阅读delegate源码

如何更新呢?

这里我加入了 React 中的 setState,当咱们调用这个方法,咱们会获得新的 data 数据,这个时候再次触发template+data -> html str -> VNode的转换.

而后使用 virtual dom 的 diff 和 patch 差别比较,修改只需改变的 DOM 元素。

总体实现

你们能够点击这里进行查看MVVM

若是能够,还请给个 star,star 是面试加分项。😂

基于咱们建立的 MVVM 的例子

  1. Count
  2. Todo App 有一点 bug, 😅

水平有限,不免有不对之处,还请指出,谢谢.😄

相关文章
相关标签/搜索