ReactNative实现 RSC Render 的解决方案

时间:2024-04-18 07:10:07

方案探索

在 React Native 中可以使用零 Bundle 大小的 React 服务器组件吗?

由于需要适应快速的产品模块发布请求,要求在App不发版的场景下,对首页的Banner进行动态更新。

当下RN所支持的热更新已经可以满足大部分需求,但是也存在两个问题

  • 强制更新影响用户体验
  • 静默更新不能及时触达

基于上述问题,我门需要一套类似服务端渲染(SSR)的方式来解决.

服务端渲染简单的可以理解成在服务端进行页面元素结构的下发(json 字符串),在前端对其解析生成对应的元素树。同样,如果使用字符串来呈现 React Native 视图,则可以创建 0kb JavaScript 的原生 React Server 组件(即不需要提前编译进客户端)。

实现

1. 定义支持渲染的前端组件

const ComponentEnum = {
    'View': View,
    'Text': Text,
    'Image': Image,
    'Button': Pressable,
    'TextInput': TextInput,
};

2.  定义组件树解析结构

// 组件
type Component {
  static type: string
  props: any
  children?: Component
}

// 点击事件
type Press {
  "@clientFn": true
  event: string
  payload: any
}

3. 解析

Object.entries(props).reduce((acc, [key, value]) => {
  if (value['@clientFn']) {
     return {
       ...acc,
        [key]: () => {
           if (isFunction(callbacks[value.event])) {
               callbacks[value.event](value.payload);
            }
        }
     }
  }
  return {
    ...acc,
    [key]: value
  };
}, {});

 4. 创建

const ComponentView = ComponentEnum[type] ?? ComponentEnum['View'];
    return createElement(
        ComponentView,
        newProps,
        isArray(children) ? Children.map(children, (child) => configToComponent(child, callbacks)) : children
    );

5. 生成 

export default new Proxy({}, {
    get: function (target, key, receiver) {
        // first time: target[key] = undefined;
        if (typeof target[key] === 'undefined') {
            // return function, ...args => Component props
            return function (...args) {
                const {data, ...props} = args[0];
                // data => server jsx
                // props => { buttonPress: [Function buttonPress]}
                return configToComponent(data, props);
            };
        } else {
            // cache component
            return Reflect.get(target, key, receiver);
        }
    }
});

 6. 声明

<SC.HomeBanner
    data={
      {
        type: 'Text',
        props: {
          style: {
            width: 100,
            height: 100,
            backgroundColor: '#f0f'
          },
          onPress: {
            "@clientFn": true,
             event: 'buttonPress',
             payload: "123"
          }
        },
        children: '123'
      }
   }
   buttonPress={(data) => console.log('button pressed', data)}
/>

缺陷

需要提前定义可支持的组件以及内置Function代码,例如埋点、点击事件的处理等。