Remax 是一個跨多端小程序 React 開發方案,之因此稱其爲「方案」而非框架是由於這並不是一個新的框架,其主要能力就是讓 React 可以直接運行在 微信小程序/支付寶小程序/字節跳動小程序/H5(固然這個原本就支持) 等環境。html
可能會有人要會問 「React 不是早就能夠運行在小程序中了麼「?本文會介紹一下現現在的一些小程序框架的解決方案,以及爲何咱們認爲把 React 直接搬進小程序是個更爲合理的方案。react
因爲大多開發者都更熟悉 React 和 Vue 的 API 和語法,加上小程序自己的開發方式確實讓人痛苦,因而便有了一些框架來將這些熟悉的語法編譯到小程序的 WXML/WXSS/JS 上,其中比較具備表明性的例如 taro,其目標就是讓開發者可以用 React 的開發方式編寫小程序。git
而這類框架的實現原理其實並不是真的是一個 React 或者類 React 框架,而是把看起來像是 JSX 的模板經過靜態編譯的方式翻譯成小程序自身的模板。github
這樣作的限制很是明顯,那就是 JSX 是 JavaScript 的拓展語言(React Blog 寫的是 is a syntax extension to JavaScript
),而小程序所採用的 WXML 倒是一個表達能力很是受限的模板語言,咱們不可能完成從一個通用編程語言到模板語言的編譯。web
而靜態編譯類框架爲了作到這一點,採起的方式就是限制開發者的寫法,這也是爲何上面稱之爲看起來像是 JSX 的模板,這也是爲何 taro 對 JSX 的寫法作出了諸多限制。npm
這種方案大多聲稱這些限制並無限制生產力,或者符合最佳實踐等等。然而咱們其實都知道這是因爲小程序自己的坑形成的,靜態編譯方案編譯的永遠都只會是模板語言,而不是 JSX。編程
之因此我說這些限制並不是基於最佳實踐,是由於 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 來開發組件
由於 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 到頁面上。
而在小程序中因爲咱們不能直接修改頁面,則由 React 完成 DIFF 後由 Remax 把修改 Patch 到內存中的虛擬 DOM 上,而後再經過小程序本身的虛擬 DOM 最後把改變同步到頁面上。
在這裏我把這個過程說得很是簡單,但其實是有些坑要填的,主要也都是來自於小程序的限制,後續會有新的文章展開來說。可是這種實現方式使得咱們徹底能夠把 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 端顯然沒有什麼問題,至於支付寶小程序目前驗證了一下可行性也是可行的。
這個項目主要由幾塊組成
因爲目前整個項目纔剛剛起步,暫時還不能用於生產環境,目前的幾個主要開發者(和打算參與的)有 @CodeFalling @bramblex @ahonn @simplyy
目前的 DEMO 能夠掃碼體驗:
或者在能夠按照 github.com/CodeFalling… 體驗本地 DEMO。
若是有人想要參與進來一塊兒開發能夠聯繫我,開發相關的細節文檔會陸續更新在 github.com/CodeFalling… 。