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 寫法:

  1. 當我們需要遍歷列表
render() {
  return ( <ul> { list.map(item => <li>item</li>) } </ul> ) } 復制代碼
  1. 當我們渲染值
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 是面試加分項。😂

基於我們創建的 MVVM 的例子

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

水平有限,難免有不對之處,還請指出,謝謝.😄




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM