HTM – JSX 的替代品?還是另一種選擇?

當耐特發表於2019-03-02

HTM – JSX 的替代品?還是另一種選擇?

Preact Omi
hyperscript tagged markup demo hyperscript tagged markup demo

原文連結

htm 全稱是 Hyperscript Tagged Markup,是一款與 JSX 語法類似的東西,相比 JSX 它最大的優點是:

  • 不需要編譯器
  • 直接在現代瀏覽器中執行,只要你的瀏覽器支援 Tagged templates 就行

所以,你可以直接在 react、preact 或者 omi 中使用並且直接在瀏覽器中執行,不需要任何編譯。它利用了 Tagged templates 和瀏覽器自帶的 HTML parser。

極小的尺寸

  • 直接在瀏覽器中使用只有 700 位元組,1KB 都不到
  • 在 preact 中使用只有 500 位元組
  • 如果使用 babel-plugin-htm 只需要 0 位元組

語法 – 像 JSX 也像 lit-html

htm 是受 lit-html 啟發,但是包含了 JSX 裡的這些特性:

  • 剩餘擴充套件: <
    div ...${props
    }>
  • 標籤自關閉: <
    div />
  • 動態標籤名: <
    ${tagName
    }>
    ( tagName 是元素的引用)
  • 布林屬性: <
    div draggable />

對 JSX 的改進

htm 確確實實地基於 JSX 之上做了大量改進,比如下面這些特性是 JSX 所沒有的:

  • 不需要編譯器,直接在瀏覽器中執行
  • HTML 的可選分號的方式: <
    div class=foo>
  • HTML 的自關閉: <
    img src=${url
    }>
  • 可選的關閉標籤: <
    section>
    <
    h1>
    this is the whole template!
  • 元件關閉標籤: <
    ${Footer
    }>
    footer content<
    //>
  • 支援 HTML 註釋: <
    div>
    <
    !-- don't delete this! -->
    <
    /div>
  • 安裝 lit-html VSCode extension 語法高亮

專案狀態

HTM最初的目標是在Preact周圍建立一個包裝器,使用它在瀏覽器中不受干擾。我想使用虛擬DOM,但我不想用構建工具,直接使用ES模組。

這意味著要放棄JSX,最接近的替代方案是 [Tagged_templates]。所以,我寫了這個庫來修補兩者之間的差異。事實證明,該技術是框架無關的,因此它應該與大多數虛擬 DOM 庫一起工作。

安裝

htm 釋出到了 npm, 也可以訪問 unpkg.com 的 CDN:

npm i htm複製程式碼

從unpkg獲取:

import htm from 'https://unpkg.com/htm?module'const html = htm.bind(React.createElement);
複製程式碼
// just want htm + preact in a single file? there's a highly-optimized version of that:import { 
html, render
} from 'https://unpkg.com/htm/preact/standalone.mjs'複製程式碼

使用指南

既然 htm 是一個通用的庫,我們需要告訴它怎麼“編譯”我們的模板。

目標應該是形式 h(tag, props, ...children) (hyperscript), 的函式,並且可以返回任何東西。

// 這是我們的 h 函式。現在,它只返回一個描述物件。function h(tag, props, ...children) { 
return {
tag, props, children
};

}複製程式碼

為了使用那個 h 函式,我們需要通過繫結htm到我們的h函式來建立我們自己的 HTML 標籤函式:

import htm from 'htm';
const html = htm.bind(h);
複製程式碼

現在我們有一個html模板標籤,可以用來生成上面建立的格式的物件,比如:

import htm from 'htm';
function h(tag, props, ...children) {
return {
tag, props, children
};

}const html = htm.bind(h);
console.log( html`<
h1 id=hello>
Hello world!<
/h1>
`
);
// {// tag: 'h1',// props: {
id: 'hello'
},
// children: ['Hello world!']//
}
複製程式碼

舉個例子

好奇地想看看這一切是什麼樣子的?這是一個工作應用程式!

它是單個HTML檔案,沒有構建或工具。你可以用Nano編輯它。

<
!DOCTYPE html>
<
html lang="en">
<
title>
htm Demo<
/title>
<
script type="module">
import {
html, Component, render
} from 'https://unpkg.com/htm/preact/standalone.mjs';
class App extends Component {
addTodo() {
const {
todos = []
} = this.state;
this.setState({
todos: todos.concat(`Item ${todos.length
}
`
)
});

} render({
page
}, {
todos = []
}) {
return 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>
`
;

}
} const Header = ({
name
}
) =>
html`<
h1>
${name
}
List<
/h1>
`
const Footer = props =>
html`<
footer ...${props
}
/>
`
render(html`<
${App
}
page="All" />
`
, document.body);
<
/script>
<
/html>
複製程式碼

這是一個Preact 線上版本.

那真是太好了?注意,只有一個匯入-這裡我們只使用了 import 與 Preact 整合,因為它更容易匯入和更小。

同樣的示例在沒有預構建版本的情況下執行良好,只需使用兩個匯入:

import { 
h, Component, render
} from 'preact';
import htm from 'htm';
const html = htm.bind(h);
render(html`<
${App
}
page="All" />
`
, document.body);
複製程式碼

其他使用方式

因為htm被設計成滿足JSX的相同需求,所以您可以使用JSX的任何地方使用它。

** 使用 vhtml 生成 HTML:**

import htm from 'htm';
import vhtml from 'vhtml';
const html = htm.bind(vhtml);
console.log( html`<
h1 id=hello>
Hello world!<
/h1>
`
);
// '<
h1 id="hello">
Hello world!<
/h1>
'
複製程式碼

Webpack configuration via jsxobj: (details here)

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'//
}
//
}
複製程式碼

omi-html

在 omi 中使用 htm

→ 線上例子

Usage of omi-html

import { 
define, render, WeElement
} from 'omi'import 'omi-html'define('my-counter', class extends WeElement {
static observe = true data = {
count: 1
} sub = () =>
{
this.data.count--
} add = () =>
{
this.data.count++
} render() {
return html` <
div>
<
button onClick=${this.sub
}
>
-<
/button>
<
span>
${this.data.count
}
<
/span>
<
button onClick=${this.add
}
>
+<
/button>
<
/div>
`

}
})render(html`<
my-counter />
`
, 'body')複製程式碼

直接執行在瀏覽器

<
script src="https://unpkg.com/omi">
<
/script>
<
script src="https://unpkg.com/omi-html">
<
/script>
<
script>
const {
define, WeElement, render
} = Omi define('my-counter', class extends WeElement {
install() {
this.constructor.observe = true this.data.count = 1 this.sub = this.sub.bind(this) this.add = this.add.bind(this)
} sub() {
this.data.count--
} add() {
this.data.count++
} render() {
return html` <
div>
<
button onClick=${this.sub
}
>
-<
/button>
<
span>
${this.data.count
}
<
/span>
<
button onClick=${this.add
}
>
+<
/button>
<
/div>
`

}
}) render(html`<
my-counter />
`
, 'body')
<
/script>
複製程式碼

Star &
Fork

來源:https://juejin.im/post/5bf61169f265da614e2bb7ae

相關文章