方案探索
在 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代码,例如埋点、点击事件的处理等。