Vue 中是如何解析 template 字符串为 VNode 的?
在接触 React 时候,我只了解到通过 babel 可以把 JSX 转成 VNode(通过调用 React.createElement 方法),但是对其具体是如何转换的却不了解。
很明显,回答失败。通过 github 上搜索 template+vnode 的关键词,让我搜到了htm库,发现简直就是我想要的。让我们看下用法:
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 成 VNODE
为什么要用 VNode?
我想这里应该是通过比较 VNode 和 DOM,并给出 VNode 的优势和 DOM 的不足。
当前 Vue 和 React 都使用了 VNode,是出于什么原因,让两大目前最火热的框架都选择使用了 VNode 呢?
这里我们直接看下写的比较好的文章吧. 深度剖析:如何实现一个 Virtual DOM 算法
了解到上面知识的大致原理后,回顾了下 React 的 JSX 写法:
- 当我们需要遍历列表
render() {
return (
<ul>
{
list.map(item => <li>item</li>)
}
</ul>
)
}
复制代码
- 当我们渲染值
render() {
return (
<p>{{ msg }}</p>
)
}
复制代码
思考了下,如果结合 ejs 等模板引擎(这些模板引擎大致的思路是结合 template+data->html->设置到 DOM 的 innerHTML),先把数据填充进去,转变成 html 字符串。
之后使用htm转成 VNode,再使用 Virtual Dom,使用 Virtual Dom 的 diff 和 patch,便可以实现了简单的 MVVM 体验。
没错,就是这么简单,废话不多说,开干吧。
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 是面试加分项。
Vue 中是如何解析 template 字符串为 VNode 的?的更多相关文章
- Scala中使用fastJson 解析json字符串
Scala中使用fastJson 解析json字符串 添加依赖 2.解析json字符 2.1可以通过JSON中的parseObject方法,把json字符转转换为一个JSONObject对象 2.2然 ...
- Vue视图渲染原理解析,从构建VNode到生成真实节点树
前言 在 Vue 核心中除了响应式原理外,视图渲染也是重中之重.我们都知道每次更新数据,都会走视图渲染的逻辑,而这当中牵扯的逻辑也是十分繁琐. 本文主要解析的是初始化视图渲染流程,你将会了解到从挂载组 ...
- 深入浅出 Vue.js 第九章 解析器---学习笔记
本文结合 Vue 源码进行学习 学习时,根据 github 上 Vue 项目的 package.json 文件,可知版本为 2.6.10 解析器 一.解析器的作用 解析器的作用就是将模版解析成 AST ...
- vue 源码学习三 vue中如何生成虚拟DOM
vm._render 生成虚拟dom 我们知道在挂载过程中, $mount 会调用 vm._update和vm._render 方法,vm._updata是负责把VNode渲染成真正的DOM,vm._ ...
- vue中引入Tinymce富文本编辑器
最近想在项目上引入一个富文本编辑器,之前引入过summernote,感觉并不太适合vue使用, 然后在网上查了查,vue中使用Tinymce比较适合, 首先,我们在vue项目的components文件 ...
- 如何在vue中使用svg
1.安装依赖 npm install svg-sprite-loader --save-dev 2.在config文件中配置 const path = require('path'); func ...
- C++解析XML字符串
项目交互遇到了需要VC++中解析XML字符串,故花了点时间了解了下VC++中解析XML的诸多方法主要包括三种:msxml(微软提供).markup.TinyXml. 开始花了点时间使用msxml3,虽 ...
- vue中v-slot使用
vue中v-slot使用 1,v-slot的使用步骤 <!-- slot.vue--> <!-- 通过name属性指定具名插槽,没有name属性的为默认插槽--> <sl ...
- Vue 中使用 TypeScript 详细总结
VUE 项目中使用 Typescript 第一节:项目起步 Vue 中使用 TypeScript 项目中主要使用到的第三方依赖 vue2 vue-class-component vue-propert ...
随机推荐
- 设置PL/SQL 快捷键
TOOLS-preferences--user interface--editor--Autoreplace--enabled (check)--address(C:\Program Files (x ...
- CSS绝对定位的原点:是在border上、padding上还是在content上?
用了那么久的绝对定位,却一直没在意一个问题,就是绝对定位的原点,究竟是在盒模型的哪一处.今天想到这个问题,直接搜索没有找到标准文档,也没有搜索到相关的问题,于是决定自己动手实现一下看看,并把这个结果发 ...
- web前端开发过程遇到的问题及解决方式(一)
1. CSS样式优先级问题 <style> .className input(可换其他元素){ type:val !important; } </style> <body ...
- 阿里云远程连接CentOS
1.购买一个CentOS的ECS服务器: 2.修改安全组,开放SSH/22的端口号: 这里是22/22为SSH连接的端口号:3389为远程桌面的默认端口号 3.利用xshell或者SecureCRT连 ...
- 作业 -- 几道简单的Python题
1.编写程序,要求生成10240个随机[0,512)之间的整数,并统计每个元素出现的次数. 2.编写程序,要求当用户输入一个列表和两个整数作为下标时,程序可以使用切片获取并输出列表中截取两个下标之间的 ...
- c语言结构体定义的几种形式
转自https://blog.csdn.net/ziguo2010/article/details/79897327 1.最常用定义方式:定义结构体data,此时结构体相当于一个类型,比如int,如需 ...
- Filter的使用(web作业)
一:什么是过滤器 Filter:Servlet过滤器Fileter是一个小型的web组件,它们通过拦截请求和响应,以便查看.提取或以某种方式操作客户端和服务器之间交换的数据,实现“过滤”的功能.Fil ...
- 负载均衡器之 Haproxy
1. 编译安装haproxy 官网: http://www.haproxy.org 1.1 下载haproxy # wget http://www.haproxy.org/download/1.6/s ...
- flask-cookie & session
Cookie @app.route('/') def hello_world(): name=request.cookies.get('Name') # 获取cookie resp = Respon ...
- Postman SMTP 存在跨站脚本(XSS)漏洞,请换用Post SMTP Mailer/Email Log
Postman SMTP 是一个安装量超过10W的WordPress插件,但是已经2年多没有更新,2017年6月29日,被发现存在跨站脚本(XSS)漏洞(查看详情),并且作者一直没有更新,所以被从Wo ...