• 在视图层显示界面

    本文将向大家介绍 Remax 的实现原理,Remax 本身分为两个部分,remaxreamx-cli,remax 提供运行时,remax-cli 提供构建功能,这里主要介绍运行时的部分。

    Remax 的运行时本质是一个通过 react-reconciler 实现的一个小程序端的渲染器。关于 react-reconciler 和 React 渲染器相关的内容推荐观看这个视频,这里不再赘述。

    大家知道,小程序对我们的代码屏蔽了 DOM,我们的代码运行在一个 worker 线程中,无法直接去操作视图层的 DOM。Remax 通过引入一层 VNode,让 React 在 reconciliation 过程中不是直接去改变 DOM,而先更新 VNode

    VNode 的基本结构如下:

    1. interface VNode {
    2. id: number;
    3. container: Container;
    4. children: VNode[];
    5. mounted: boolean;
    6. type: string | symbol;
    7. props?: any;
    8. parent: VNode | null;
    9. text?: string;
    10. appendChild(node: VNode): void;
    11. removeChild(node: VNode): void;
    12. insertBefore(newNode: VNode, referenceNode: VNode): void;
    13. toJSON(): RawNode;
    14. }
    • id - 节点 id,这是一个自增的唯一 id,用于标识节点。
    • container - 类似 ReactDOM.render(<App />, document.getElementById('root') 中的第二个参数,Remax 中会把组件渲染到一个容器中,容器的作用是保存 VNode 的引用。
    • children - 子节点。
    • mounted- 标识节点是否已经显示到视图层上。
    • type - 节点的类型,也就是小程序中的基础组件,如:viewtext等等。
    • props - 节点的属性。
    • parent - 父节点。
    • text - 文本节点上的文字。

    可以看到,VNode 也是一个树结构,我们在 VNode 上实现了类似 DOM 中的节点操作方法。在 React 的更新完成后,我们会调用节点上的 toJSON 方法,把这个 VNode 变成一个 JSON 对象。

    举个例子,假设我们有这样一个页面组件:

    1. import React from 'react';
    2. import { View, Text } from 'remax/alipay';
    3. const IndexPage = () => {
    4. return (
    5. <View className="greeting">
    6. <Text>Hello Remax</Text>
    7. </View>
    8. );
    9. };
    10. export default IndexPage;

    Remax 在渲染这个组件时,会把它渲染成如下的 VNode 结构:

    1. {
    2. "id": 0,
    3. "type": "root",
    4. "children": [
    5. {
    6. "id": 1,
    7. "type": "view",
    8. "props": {
    9. "className": "greeting"
    10. },
    11. "children": [
    12. {
    13. "id": 2,
    14. "type": "text",
    15. "props": {},
    16. "children": [
    17. {
    18. "type": "plain-text",
    19. "text": "Hello Remax"
    20. }
    21. ]
    22. }
    23. ]
    24. }
    25. ]
    26. }

    其中 root 节点是由 Remax 内部创建的,这个渲染出来的 VNode 数据就会成为小程序 Pagedata

    具体这部分的源码实现可以参考下面三个文件:

    • hostConfig.ts
    • Container.ts
    • VNode.ts

    在视图层显示界面

    上面讲到我们的 React 组件最终会被渲染成一个我们称之为 VNode 的 JSON 对象,并且这个对象会作为小程序 Pagedata。现在我们要做的就是在小程序的模板里怎么把这个 data 给显示出来了。

    我们在 remax-cli 构建我们的 Remax 代码时  生成一个页面模板显示这个 VNode,这个模板大概是下面这个样子:

    1. <block a:for="{{root.children}}" a:key="{{item.id}}">
    2. <template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" />
    3. </block>
    4. <template name="REMAX_TPL_view">
    5. <view class="{{item.props['className']}}">
    6. <block a:for="{{item.children}}" key="{{item.id}}">
    7. <template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" />
    8. </block>
    9. </view>
    10. </template>
    11. <template name="REMAX_TPL_text">
    12. <text>
    13. <block a:for="{{item.children}}" key="{{item.id}}">
    14. <template is="{{'REMAX_TPL_' + item.type}}" data="{{item: item}}" />
    15. </block>
    16. </text>
    17. </template>
    18. <template name="REMAX_TPL_plain-text">
    19. <block>{{item.text}}</block>
    20. </template>

    可以看到,我们会先去遍历根节点的子元素,再根据每个子元素的类型选择对应的模板来渲染子元素,然后在每个模板中我们又会去遍历当前元素的子元素,以此把整个节点树递归遍历出来。

    值得注意的是,在微信小程序模板是不支持递归的,所以 Remax 会为微信小程序生成一个 20 层的模板调用。

    以上就是 Remax 实现的基本原理,在具体实现上我们还会去做一些优化,想深入了解的同学可以直接看代码。