精讀《Htm - Hyperscript 原始碼》
ofollow,noindex">htm 是 preact 作者的新嘗試,利用原生 HTML 規範支援了類 JSX 的寫法。
2 概要
htm 沒有特別的文件,假如你用過 JSX,那隻需要記住下面三個不同點:
-
className
->class
。 -
標籤引號可選(迴歸 html 規範):
<div class=foo>
。 -
支援 HTML 模式的註釋:
<div><!-- don't delete this! --></div>
。
另外支援了可選結束標籤、快捷元件 End 標籤,不過這些自己發明的語法不建議記憶。
用法也沒什麼特別的地方,你可以利用 HTML 原生規範,用直覺去寫 JSX:
html` <div class="app"> <${Header} name="ToDo's (${page})" /> <ul> ${todos.map( todo => html` <li>${todo}</li> ` )} </ul> <button onClick=${() => this.addTodo()}>Add Todo</button> <${Footer}>footer content here<//> </div> `; 複製程式碼
很顯然,由於跳過了 JSX 編譯,換成了原生的Template Strings ,所以所有元件、屬性部分都需要改成${}
語法,比如:
<${Header}>
這種寫法略顯彆扭,但整體上還是蠻直觀的。
你不一定非要用在專案環境中,但當你看到這種語法時,內心一定情不自禁的 WoW,竟然還有這種寫法!
下面將帶你一起分析htm 的原始碼,看看作者是如何做到的。
3 精讀
你可以先自己嘗試閱讀,原始碼加上註釋一共 90 行:原始碼 。
好了,歡迎繼續閱讀。
首先你要認識到,htm
+vhtml
才等於你上面看到的 DEMO。
Htm
Htm
是一個 dom template 解析器,它可以將任何 dom template 解析成一顆語法樹,而這個語法樹的結構是:
interface VDom { tag: string; props: { [attrKey: string]: string; }; chindren: VDom[]; } 複製程式碼
我們看一個 demo:
function h(tag, props, ...children) { return { tag, props, children }; } const html = htm.bind(h); html` <div>123</div> `; // { tag: "div", props: {}, children: ["123"] } 複製程式碼
那具體是怎麼做語法解析的呢?
其實實現方式有點像腦經急轉彎,畢竟解析 dom template 是瀏覽器引擎做的事,規範也早已定了下來,有了規範和實現,當然沒必要重複造輪子,辦法就是利用 HTML 的 AST 生成我們需要的 AST。
首先建立一個template
元素:
const TEMPLATE = document.createElement("template"); 複製程式碼
再裝輸入的 dom template 字串塞入(作者通過正則,機智的將自己支援的額外語法先轉化為標準語法,再交給 HTML 引擎):
TEMPLATE.innerHTML = str; 複製程式碼
最後我們會發現進入了walk
函式,通過localName
拿到標籤名;attributes
拿到屬性值,通過firstChild
與nextSibling
遍歷子元素繼續走walk
,最後tag
props
children
三劍客就生成了。
可能你還沒看完,就已經結束了。筆者分析這個庫,除了告訴你作者的機智思路,還想告訴你的是,站在巨人的肩膀造輪子,真的事半功倍。
VDom
VDom 是個抽象概念,它負責將實體語法樹解析為 DOM。這個工具可以是 preact、vhtml,或者由你自己來實現。
當然,你也可以利用這個 AST 生成 JSON,比如:
import htm from "htm"; import jsxobj from "jsxobj"; const html = htm.bind(jsxobj); console.log(html` <webpack watch mode=production> <entry path="src/index.js" /> </webpack> `); // { //watch: true, //mode: 'production', //entry: { //path: 'src/index.js' //} // } 複製程式碼
讀到這,你覺得還有哪些 “VDom” 可以寫呢?其實任何可以根據tag
props
children
推匯出的結構都可以寫成解析外掛。
4 總結
htm 是一個教科書般借力造論子案例:
innerHTML
不過這也帶來了一個問題:依賴原生 DOM API 會導致無法執行在 NodeJS 環境。
想一想你現在開發的工具庫,有沒有可以借力的地方呢?有哪些點可以通過借力做得更好從而實現雙贏呢?歡迎留下你的思考。
討論地址是:精讀《Htm - Hyperscript 原始碼》 · Issue #114 · dt-fe/weekly
如果你想參與討論,請點選這裡 ,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。