本文將向大家介紹 Remax 的實(shí)現(xiàn)原理,Remax 本身分為兩個(gè)部分,remax
和 reamx-cli
,remax
提供運(yùn)行時(shí),remax-cli
提供構(gòu)建功能,這里主要介紹運(yùn)行時(shí)的部分。
Remax 的運(yùn)行時(shí)本質(zhì)是一個(gè)通過 react-reconciler
實(shí)現(xiàn)的一個(gè)小程序端的渲染器。關(guān)于 react-reconciler
和 React 渲染器相關(guān)的內(nèi)容推薦觀看這個(gè)視頻,這里不再贅述。
大家知道,小程序?qū)ξ覀兊拇a屏蔽了 DOM,我們的代碼運(yùn)行在一個(gè) worker 線程中,無法直接去操作視圖層的 DOM。Remax 通過引入一層 VNode
,讓 React 在 reconciliation 過程中不是直接去改變 DOM,而是更新 VNode
。
VNode
的基本結(jié)構(gòu)如下:
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é)點(diǎn) id,這是一個(gè)自增的唯一 id,用于標(biāo)識節(jié)點(diǎn)。container
- 類似 ReactDOM.render(<App />, document.getElementById('root')
中的第二個(gè)參數(shù),Remax 中會把組件渲染到一個(gè)容器中,容器的作用是保存 VNode
的引用。children
- 子節(jié)點(diǎn)。mounted
- 標(biāo)識節(jié)點(diǎn)是否已經(jīng)顯示到視圖層上。type
- 節(jié)點(diǎn)的類型,也就是小程序中的基礎(chǔ)組件,如:view
、text
等等。props
- 節(jié)點(diǎn)的屬性。parent
- 父節(jié)點(diǎn)。text
- 文本節(jié)點(diǎn)上的文字。可以看到,VNode
也是一個(gè)樹結(jié)構(gòu),我們在 VNode
上實(shí)現(xiàn)了類似 DOM
中的節(jié)點(diǎn)操作方法。在 React 的更新完成后,我們會調(diào)用節(jié)點(diǎn)上的 toJSON
方法,把這個(gè) VNode
變成一個(gè) JSON 對象。
舉個(gè)例子,假設(shè)我們有這樣一個(gè)頁面組件:
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 在渲染這個(gè)組件時(shí),會把它渲染成如下的 VNode
結(jié)構(gòu):
{
"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é)點(diǎn)是由 Remax 內(nèi)部創(chuàng)建的,這個(gè)渲染出來的 VNode
數(shù)據(jù)就會成為小程序 Page
的 data
。
具體這部分的源碼實(shí)現(xiàn)可以參考下面三個(gè)文件:
上面講到我們的 React 組件最終會被渲染成一個(gè)我們稱之為 VNode
的 JSON 對象,并且這個(gè)對象會作為小程序 Page
的 data
?,F(xiàn)在我們要做的就是在小程序的模板里怎么把這個(gè) data
給顯示出來了。
我們在 remax-cli
構(gòu)建我們的 Remax 代碼時(shí) 生成一個(gè)頁面模板顯示這個(gè) VNode
,這個(gè)模板大概是下面這個(gè)樣子:
<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é)點(diǎn)的子元素,再根據(jù)每個(gè)子元素的類型選擇對應(yīng)的模板來渲染子元素,然后在每個(gè)模板中我們又會去遍歷當(dāng)前元素的子元素,以此把整個(gè)節(jié)點(diǎn)樹遞歸遍歷出來。