在小程式中使用 React with Hooks

當軒發表於2019-03-04

原文連結:github.com/CodeFalling…

介紹一下 Remax

Remax 是一個跨多端小程式 React 開發方案,之所以稱其為“方案”而非框架是因為這並非一個新的框架,其主要能力就是讓 React 能夠直接執行在 微信小程式/支付寶小程式/位元組跳動小程式/H5(當然這個本來就支援) 等環境。

可能會有人要會問 “React 不是早就可以執行在小程式中了麼“?本文會介紹一下現如今的一些小程式框架的解決方案,以及為什麼我們認為把 React 直接搬進小程式是個更為合理的方案。

靜態編譯類框架

由於大多開發者都更熟悉 React 和 Vue 的 API 和語法,加上小程式本身的開發方式確實讓人痛苦,於是便有了一些框架來將這些熟悉的語法編譯到小程式的 WXML/WXSS/JS 上,其中比較具有代表性的例如 taro,其目標就是讓開發者能夠用 React 的開發方式編寫小程式。

而這類框架的實現原理其實並非真的是一個 React 或者類 React 框架,而是把看起來像是 JSX 的模板通過靜態編譯的方式翻譯成小程式自身的模板。

這樣做的限制非常明顯,那就是 JSX 是 JavaScript 的擴充語言(React Blog 寫的是 is a syntax extension to JavaScript),而小程式所採用的 WXML 卻是一個表達能力非常受限的模板語言,我們不可能完成從一個通用程式語言到模板語言的編譯。

而靜態編譯類框架為了做到這一點,採取的方式就是限制開發者的寫法,這也是為什麼上面稱之為看起來像是 JSX 的模板,這也是為什麼 taro 對 JSX 的寫法做出了諸多限制。

image
image
image
image

這種方案大多聲稱這些限制並沒有限制生產力,或者符合最佳實踐等等。然而我們其實都知道這是由於小程式本身的坑造成的,靜態編譯方案編譯的永遠都只會是模板語言,而不是 JSX。

React Hooks

之所以我說這些限制並非基於最佳實踐,是因為 React 本身對於 JSX 的定位就 並非模板

JSX is a syntax extension to JavaScript.

在最近 React 團隊已經向我們介紹了 Hooks,期望可以 functional component 不僅僅可以是無狀態元件,也可以是 useState 的。

import { useState } from `react`;

function Example() {
  // Declare a new state variable, which we`ll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
複製程式碼

React 官方部落格提到 Classes confuse both people and machines,我們也明顯可以看到基於 function 的元件明顯更為簡潔,噪聲更小,未來 React 社群的方向更是會逐漸從 class component 過渡到 functional component。

在這種趨勢下,把 JSX 當做模板寫,且未來永遠也不可能支援 functional component 的方案絕非真的基於最佳實踐的選擇。

在 Remax 中,我們完全可以使用全新的 Hooks API 來開發元件

image

因為 Remax 中的 React 就是 React.js,而 JSX 就是 JavaScript 的超集。

上圖中使用小程式的原生語法,classname 和 inline style 就只能寫成

<view class="weui-navbar__item {{activeIndex == index ? `weui-bar__item_on` : ``}}">
</view>
<view style="left: {{sliderLeft}}px; transform: translateX({{sliderOffset}}px); -webkit-transform: translateX({{sliderOffset}}px);"></view>
複製程式碼

而使用 remax 後就可以寫成正常的 react:

 const innerStyle = {
    left: `${sliderLeft}px`,
    transform: `translateX(${sliderOffset}px);`,
    `-webkit-transform`: `translateX(${sliderOffset}px)`,
    width: sliderWidth,
  };
const itemClassName = classnames({
   `weui-navbar__item`: true,
    `weui-bar__item_on`: activeIndex === index,
});

return <View className={itemClassName}>
    <View style={innerStyle} />
    </View>
複製程式碼

實現原理

核心部分

Remax 的實現原理和基於靜態編譯的方案有所不同,其核心其實是重新實現了 ReactDOM 的部分。

眾所周知,React 本身的設計就是支援跨端渲染的,render 部分和 React 的核心邏輯是解耦的(甚至不在一個 npm 包裡)。主要的 render 有 ReactDOM(瀏覽器),ReactDOMServer(伺服器端)和 ReactNative。

Remax 要做的事情和 ReactNative 要做的事情非常類似,我們重新接管了 ReactDOM 的 render。

在原有的 React 頁面中,React 在完成 Diff 發現需要修改介面時,又 ReactDOM 把改變 Patch 到頁面上。

image

而在小程式中由於我們不能直接修改頁面,則由 React 完成 DIFF 後由 Remax 把修改 Patch 到記憶體中的虛擬 DOM 上,然後再通過小程式自己的虛擬 DOM 最後把改變同步到頁面上。

image

在這裡我把這個過程說得非常簡單,但實際上是有些坑要填的,主要也都是來自於小程式的限制,後續會有新的文章展開來講。但是這種實現方式使得我們完全可以把 React 的程式碼放在小程式的環境中執行。

工程化

工程化很理所當然的用 Webpack 來實現, 除了我們常用的打包等功能外,Webpack 外掛也使我們很容易構建一些我們需要的東西出來,例如我們需要在每個 js 入口除了放一個 js 外還需要新增一個 wxml 檔案,就可以通過一個很簡單的 Webpack 外掛來實現。

function GeneraeWxmlWebpackPlugin() {
  const content = `<view>...</view>`;
  const apply = (compiler) => {
    const emit = (compilation, cb) => {
      const {
        chunks,
      } = compilation;
      chunks.forEach((item) => {
        compilation.assets[`${item.name}.wxml`] = {
          source: () => content,
          size: () => content.length,
        };
      });

      cb();
    };

    if (compiler.hooks) {
      const plugin = { name: `GeneraeWxmlWebpackPlugin` };
      compiler.hooks.emit.tapAsync(plugin, emit);
    } else {
      compiler.plugin(`emit`, emit);
    }
  };

  return {
    apply,
  };
}
複製程式碼

跨端

這種方案想實現同一套程式碼跨到 H5 端顯然沒有什麼問題,至於支付寶小程式目前驗證了一下可行性也是可行的。

miniapp1
1547346366247-2ff00034-c5fc-449a-bc5c-7c65302c96b6

專案結構

這個專案主要由幾塊組成

  • @remax/core
    核心部分,負責 React 元件的 render
  • @remax/cli
    顧名思義,CLI 工具,用於構建生成相應的小程式專案等工作
  • @remax/components
    底層 Component,包括諸如 View 等一些基礎元件,用於抹平不同環境的差異
  • @remax/ui
    自帶的基礎元件庫,這部分還待開發,目前只有一兩個示例元件

由於目前整個專案才剛剛起步,暫時還不能用於生產環境,目前的幾個主要開發者(和打算參與的)有 @CodeFalling @bramblex @ahonn @simplyy

目前的 DEMO 可以掃碼體驗:

image

或者在可以按照 github.com/CodeFalling… 體驗本地 DEMO。

如果有人想要參與進來一起開發可以聯絡我,開發相關的細節文件會陸續更新在 github.com/CodeFalling…

討論群

image

推薦閱讀

相關文章