使用新一代js模板引擎NornJ提升React.js開發體驗

joe_sky發表於2018-04-11

當前的前端世界中有很多著名的開源javascript模板引擎HandlebarsNunjucksEJS等等,相信很多人對它們都並不陌生。

js模板引擎的現狀

通常來講,這些js模板引擎專案都有一個共同的特性:只專注渲染字串(html)

早在幾年前Backbone等mv*框架流行的時候,js模板引擎遇到了它們的春天,因為Backbone可以支援選配使用者自己喜好的模板,並提供了接入方案。但是在新一代前端mv*框架盛行的今天,人們更多的關注點在於React的JSX支援的邏輯何等地強大、Vue的v-show等指令使用起來多麼地方便,而js模板引擎呢?它們似乎只能在Node.js伺服器端找到它們的歸宿,而且還被React及Vue的 SSR(服務端渲染) 繼續蠶食著僅有的市場。

為什麼各種各樣的js模板引擎都只專注於渲染html字串?這或許跟歷史原因有關,畢竟5、6年前的時候並沒有虛擬dom,使用jQuery等框架的$('div').html(str)方法渲染dom是理所當然的事情。

新型js模板引擎

我們不妨試想一下,其實js模板引擎在當前的時代只要也能做到渲染虛擬dom物件,或許就可以再次找到它們被重用的機會:

         +---------------------+
         ¦ <Template string /> ¦
         +---------------------+
                    |
                    |
         +---------------------+
         |      render to      |
         |                     |
  +-------------+    +-------------------+
  ¦ html string ¦    ¦ React virtual dom ¦
  +-------------+    +-------------------+
複製程式碼

然而目前有一個新的js模板引擎可以做到上述的同時支援渲染html和React元件,它就是NornJ

github:github.com/joe-sky/nor…

官方文件(github pages):joe-sky.github.io/nornj-guide

官方文件(gitbook):joe-sky.gitbooks.io/nornj-guide

安裝

npm install nornj
npm install nornj-react   # React開發請一起安裝此包
npm install nornj-loader  # webpack環境請一起安裝此包
複製程式碼

線上演示地址

渲染html字串

渲染React元件

在React開發中的基本使用方法

React在介紹自己時常說JSX是"可選的",但實際上,脫離了JSX的React根本就幾乎無法正常地開發。如果有了另一種DSL(js模板引擎)可適配React開發,那麼JSX才能真正地成為可選的技術。

NornJ的模板語法在參考自HandlebarsNunjucksVue等多個著名專案的基礎上,也有很多自己獨特的語法如tagged template string自定義語句與運算子等等,與html+js非常相似可快速上手。需要提一下另一個React的模板專案react-templates,它是React生態中唯一一個比較完善的模板專案,但很可惜的是它現在已經幾乎不維護了,而且功能非常有限。

每個React元件都須要在render返回元件的標籤程式碼,如在HelloWorld元件中渲染一個下拉框,用JSXNornJ的語法分別實現:

  • JSX
export default class HelloWorld extends Component {
  render() {
    return (
      <div className="hello" style={{ width: 300, height: 200 }}>
        <input type="text" />
        <select>
          {[1, 2, 3].map((item, i) => i > 1
            ? <option>{item + 1}</option>
            : <option>{item}</option>
          )}
        </select>
      </div>
    );
  }
}
複製程式碼
  • NornJ
import { template as t } from 'nornj';
import 'nornj-react';
import { Input } from 'antd';

export default class HelloWorld extends Component {
  render() {
    return t`
      <div class="hello" style="width:300px;height:200px;">
        <input type="text">
        <select>
          <#each {1 .. 3}>
            <#if {@index > 1}>
              <option>{@item + 1}</option>
              <#else><option>{@item}</option></#else>
            </#if>
          </#each>
        </select>
      </div>
      <${Input} placeholder="Basic usage" />
    `;
  }
}
複製程式碼

如上例,這就是NornJ最基本的使用方法了,開箱即用。它可以使用ES6+tagged template literals語法在js檔案中描述模板,模板語法在處理邏輯時的結構比JSX更加易讀,且語法和html更為接近:

  • 可以寫class替代className。
  • style可以使用html中寫style屬性的方式,當然寫物件也同樣支援。
  • 模板語法提供了#if#each等擴充套件標籤用於處理邏輯,可替代三目運算子陣列map方法
  • input和img等標籤支援只寫開標籤,如<input type="text">,JSX中一定要寫為<input type="text" />
  • 可直接在元件的render中返回同一級別的多個標籤,外面不用套上陣列。
  • 雙花括號{{}}和單花括{}號語法在React開發中都支援,除特殊場景外依個人喜好而定。
  • 模板和JSX一樣支援嵌入任意js變數,這當然也包含第三方React元件和JSX變數,NornJ模板和JSX是可以共存的!

NornJtagged template literals語法更多細節請檢視官方文件

上面的例子也可以這樣改寫:

import nj from 'nornj';
import 'nornj-react';
import { Input } from 'antd';

const tmplFn = nj`
  <div class="hello" style="width:300px;height:200px;">
  ...
  </div>
  <${Input} placeholder="Basic usage" />
`;

export default class HelloWorld extends Component {
  render() {
    return tmplFn();
  }
}
複製程式碼

可以看出,實質上tagged template literals語法就是建立了一個模板函式,然後再在render中執行了而已。這時不難想到,使用第一種方法將模板函式放到render中執行,這樣會不會每次執行render時都進行模板編譯(內部涉及各種正則析取)會造成效能下降?並不會,因為NornJ模板在編譯時會進行快取,只有第一次render時會進行模板編譯,之後的每次render就會走快取了。

另外,NornJJSX還可以巢狀編寫,僅僅在很小的粒度使用NornJ模板也完全沒有問題,具體請見官方文件

單檔案模板

NornJ模板除了可以在js檔案中編寫之外,還可以編寫在單獨的模板檔案中,用來做元件(或頁面)展現層與結構層的分離(具體請參考官方文件)。例如編寫一個helloWorld.nj.html檔案:

<template name="partial">
  <ant-Input placeholder="Basic usage" value={value} />
</template>

<template name="helloWorld">
  <div class={styles.hello}>
    <select>
      <#each {1 .. 3}>
        <#if {@index > 1}>
          <option>{@item + 1}</option>
          <#else><option>{@item}</option></#else>
        </#if>
      </#each>
    </select>
  </div>
  <#include name="partial" />
</template>
複製程式碼

然後可以在js檔案中引入後使用:

import tmpls from './helloWorld.nj.html';

export default class HelloWorld extends Component {
  state = {
    value: 'test'
  };

  render() {
    return tmpls.helloWorld(this.state, this.props);
  }
}
複製程式碼

如上,每個*.nj.html檔案內都可以定義一個或多個template標籤。這些template標籤會在引用它的js檔案中通過nornj-loader進行解析,生成一個以template標籤的name屬性為key的模板函式集合物件,在各個元件的render中呼叫它們就會生成相應的React vdom物件。

針對NornJ單檔案模板,我們也提供了一些IDE的語法高亮與提示工具

擴充套件模板

NornJHandlebars比較類似具有非常強大的可擴充套件性,#if#each等實際上都是擴充套件出來的語法,您完全可以自己擴充套件出#customIf#customEach等新語句。在可擴充套件性這一點上,不難想到JSX也可以通過babel進行擴充套件,也可以搞新的語法出來,例如jsx-control-statements。但是babel擴充套件上手門檻不低,要學各種babel AST的用法,開發一個完美的外掛出來似乎並非易事。

由於NornJ繼承於Handlebars的擴充套件方式,它內部的每個擴充套件都可以用一個函式簡單地開發出來,例如為NornJ擴充套件一個**運算子,作用是乘方運算:

import nj from 'nornj';

nj.registerFilter('**', (val1, val2) => Math.pow(val1, val2));
複製程式碼

然後就可以直接使用了:

<input value="{ 2 ** 10 / 100 }">
複製程式碼

當然上述只是個最簡單的例子,更多模板擴充套件描述請參考官方文件

結合Mobx建立雙向資料繫結

利用NornJ的可擴充套件性,模板語法在理論上可以實現無限的可能性。#mobx-modelNornJ實現的一個行內擴充套件標籤(類似於vue及ng的指令),具體用法如下:

import { Component } from 'react';
import { observable } from 'mobx';
import nj from 'nornj';
import 'nornj-react';
import 'nornj-react/mobx';

class TestComponent extends Component {
  @observable inputValue = '';

  render() {
    return nj`<input :#mobx-model="inputValue">`(this);
  }
}
複製程式碼

#mobx-model的底層實現方式和Vue的v-model是比較類似的。React也有其他雙向繫結的實現如Mota,但該專案的實現方式是通過高階元件。利用NornJ的擴充套件語法,我們還能實現更多類似於#mobx-model的擴充套件功能。

結合React的各種生態

NornJ可以完美結合各種React生態,包括React-NativeReduxReact-RouterMobxAnt Design等等,它可以和任何已有的React生態共存。

更多詳細文件請見官方文件

適配各種React-Like庫

NornJ在理論上可以適配任意React-Like庫,包括PreactinfernoanuNerv等。

具體適配方式請見官方文件

渲染html字串

NornJ同時還支援渲染html字串,這和傳統的js模板引擎就完全一樣了。使用方法和React中幾乎完全一樣,具體請看這個線上例項:

當然,NornJ也能夠支援Node.js伺服器ExpressKoa等。傳統js模板的compilerender等方法,NornJ也支援。

更多細節請見官方文件

NornJ的未來計劃

NornJ在未來我們還有很多可以增強的方面,例如:

  • 開發eslint外掛

NornJ目前雖然內部有語法錯誤警告機制,但只是將錯誤列印在控制檯。對於靜態語法錯誤檢測,NornJ將來有必要開發一個eslint外掛。

  • 效能持續優化

雖然NornJ目前的模板渲染效率已然不低(請見模板渲染效率測試),但仍尚有很大的優化空間,會持續進行優化工作。

  • 國際化

  • 對Vue提供適配

NornJ適配Vue暫時是個設想,理論上是可以實現的。但有幾個難題:

  1. Vue使用的虛擬dom物件結構並非React那樣簡單,它使用類似snabbdom的結構,其中的事件等方法都綁在特殊的物件上。這對NornJ來說適配起來和React比有一定難度。
  2. Vue的模板語法已然很好用,雖然NornJ可以提供一些自身獨特的語法,但是提升開發體驗的作用恐怕沒有在React中那樣明顯。

NornJ發展到今天已經擁有了很多強大的功能,日後還會繼續完善,歡迎試用。

相關文章