本文將向大家介紹 Remax 的實現原理,Remax 本身分為兩個部分,remax
和 remax-cli
,remax
提供運行時,remax-cli
提供構建功能,這里主要介紹運行時的部分。
Remax 的運行時本質是一個通過 react-reconciler
實現的一個小程序端的渲染器。關于 react-reconciler
和 React 渲染器相關的內容推薦觀看這個視頻,這里不再贅述。
大家知道,小程序對我們的代碼屏蔽了 DOM,我們的代碼運行在一個 worker 線程中,無法直接去操作視圖層的 DOM。Remax 通過引入一層 VNode
,讓 React 在 reconciliation 過程中不是直接去改變 DOM,而先更新 VNode
。
VNode
的基本結構如下:
interface VNode {id: number;container: Container;children: VNode[];mounted: boolean;type: string | symbol;props?: any;parent: VNode | null;text?: string;appendChild(node: VNode): void;removeChild(node: VNode): void;insertBefore(newNode: VNode, referenceNode: VNode): void;toJSON(): RawNode;}
id
- 節(jié)點 id,這是一個自增的唯一 id,用于標識節(jié)點。container
- 類似 ReactDOM.render(<App />, document.getElementById('root')
中的第二個參數,Remax 中會把組件渲染到一個容器中,容器的作用是保存 VNode
的引用。children
- 子節(jié)點。mounted
- 標識節(jié)點是否已經顯示到視圖層上。type
- 節(jié)點的類型,也就是小程序中的基礎組件,如:view
、text
等等。props
- 節(jié)點的屬性。parent
- 父節(jié)點。text
- 文本節(jié)點上的文字。可以看到,VNode
也是一個樹結構,我們在 VNode
上實現了類似 DOM
中的節(jié)點操作方法。在 React 的更新完成后,我們會調用節(jié)點上的 toJSON
方法,把這個 VNode
變成一個 JSON 對象。
舉個例子,假設我們有這樣一個頁面組件:
import React from 'react';import { View, Text } from 'remax/alipay';const IndexPage = () => {return (<View className="greeting"><Text>Hello Remax</Text></View>);};export default IndexPage;
Remax 在渲染這個組件時,會把它渲染成如下的 VNode
結構:
{"id": 0,"type": "root","children": [{"id": 1,"type": "view","props": {"className": "greeting"},"children": [{"id": 2,"type": "text","props": {},"children": [{"type": "plain-text","text": "Hello Remax"}]}]}]}
其中 root
節(jié)點是由 Remax 內部創(chuàng)建的,這個渲染出來的 VNode
數據就會成為小程序 Page
的 data
。
具體這部分的源碼實現可以參考下面三個文件:
上面講到我們的 React 組件最終會被渲染成一個我們稱之為 VNode
的 JSON 對象,并且這個對象會作為小程序 Page
的 data
?,F在我們要做的就是在小程序的模板里怎么把這個 data
給顯示出來了。
我們在 remax-cli
構建我們的 Remax 代碼時 生成一個頁面模板顯示這個 VNode
,這個模板大概是下面這個樣子:
<block a:for="{{root.children}}" a:key="{{item.id}}"><template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" /></block><template name="REMAX_TPL_view"><view class="{{item.props['className']}}"><block a:for="{{item.children}}" key="{{item.id}}"><template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" /></block></view></template><template name="REMAX_TPL_text"><text><block a:for="{{item.children}}" key="{{item.id}}"><template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" /></block></text></template><template name="REMAX_TPL_plain-text"><block>{{item.text}}</block></template>
可以看到,我們會先去遍歷根節(jié)點的子元素,再根據每個子元素的類型選擇對應的模板來渲染子元素,然后在每個模板中我們又會去遍歷當前元素的子元素,以此把整個節(jié)點樹遞歸遍歷出來。
以上就是 Remax 實現的基本原理,在具體實現上我們還會去做一些優(yōu)化,想深入了解的同學可以直接看代碼。
更多建議: