PC 站点 React 组件适配移动端的方案选择思考

目前所在的是一个面向 PC 的 B 端平台业务,React 技术栈,使用的 Semi-UI 组件库。在业务发展过程中,发现有大量用户是通过移动端设备来访问的,这使得我们有需求优化站点在移动端的使用体验。

出于业务特征,我们不适合单独开发一套移动端页面出来,因此想要尽量低成本的优化站点在移动端的体验。那么,响应式的方式对页面布局进行动态调整就是一个不错的选择。

但页面的布局调整只能满足最基础的移动端优化,想要有更好的体验,需要我们在组件层面实现细节的优化。

例如 Semi-UI 中的选择器组件,点击后会弹出一个 popover 来展现所有的选项。在 PC 端这一交互是很符合逻辑的,

image.png

但如果我们在移动端较小的屏幕下也使用这种交互,体验就不尽如人意了。我们希望在移动端 select 可以自动变成下方弹出的 sidesheet。

image.png

所以我们需要做的是在组件层面,PC 端和移动端渲染不同的样式。这里我们遇到的一个现实情况是当前组件的写法很多,其子组件有通过 children 来传递的,也有通过 props 来传递的,改造成本需要尽量低。为了便于理解,我们假设有一个 Select 组件,它是这样使用的:

1
2
3
4
5
6
<Select defaultValue="abc" style={{ width: 120 }}>
<Select.Option value="abc">抖音</Select.Option>
<Select.Option value="ulikecam">轻颜相机</Select.Option>
<Select.Option value="jianying" disabled>剪映</Select.Option>
<Select.Option value="xigua">西瓜视频</Select.Option>
</Select>

方法一:单独开发一套移动端组件

一个朴素的想法是我们直接独立于 Semi-UI,开发一套移动端组件,业务在使用时自己判断是否在移动端,选择不同的组件。比如我们可以单独开发一个 MobileSelect 组件,但此时我们的移动端组件由于需要改为 sidesheet,因此需要以配置项的形式传入 options 参数,组件内部读取配置,渲染 sideSheet。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (isMobile) {
return <SelectMobile defaultValue="abc" style={{ width: 120 }} options={[
{value: "abc", text: "抖音”},
{value:"ulikecam", text: "轻颜相机"},
{value:"jianying", text: "剪映", disabled: true},
{value:"xigua">西瓜视频</Select.Option>
]}>
</SelectMobile>;
}
return <Select defaultValue="abc" style={{ width: 120 }}>
<Select.Option value="abc">抖音</Select.Option>
<Select.Option value="ulikecam">轻颜相机</Select.Option>
<Select.Option value="jianying" disabled>剪映</Select.Option>
<Select.Option value="xigua">西瓜视频</Select.Option>
</Select>

显而易见如果采用这种方式,目前业务代码中所有使用到 <Select/> 组件的地方都需要实现一遍,成本是非常大的,增加了复杂度。

当然这种方式也不是只有缺点:它相对简单直接,实现难度比较低;同时看代码就可以很清楚的知道做了什么事情,不会给人造成意外的结果;这种方法也可以尽可能地保证 PC 端的逻辑不发生变化,这样可以使得我们在实现过程中,不对现有 PC 端的质量造成影响。

方法二:统一入口组件,使用 JSON 配置传递 props

上面的代码主要问题是改造成本很大,业务开发者需要针对移动端和 PC 端做判断使用不同的组件。那么一个想法就是我们可以把移动端与 PC 端的判断逻辑收敛到 Select 组件内部,在移动端使用 sideSheet 而在 PC 端使用原始的 Select。那么 Select 的使用就变成了:

1
2
3
4
5
6
7
8
9
10
<Select
defaultValue="abc"
style={{ width: 120 }}
options={[
{value: "abc", text: "抖音”},
{value:"ulikecam", text: "轻颜相机"},
{value:"jianying", text: "剪映", disabled: true},
{value:"xigua">西瓜视频</Select.Option>
]}
></Select>;

注意这里我们虽然不再用判断是移动端还是 PC 端写两份代码了,但我们还是需要把所有的 option 改为使用 props 的形式来传递,从而可以在组件内部读取到 options 的值。

这种实现方式,改动量相比之前的两份代码要小很多,但依旧还是需要在业务的各个地方修改,把按照子元素方式写的 Options 改为参数形式。

方法三:统一入口组件,修改 children

进一步,我们可以直接利用 children 参数,在不修改写法的情况下,直接读取子元素的 props 来进行渲染。

React 为我们提供了 Children 工具集:https://react.dev/reference/react/Children

使用这种方法,我们就可以保持 Select 的业务写法不变,从 children 中读取 props 来做移动端的特殊展现。

1
2
3
4
5
6
<Select defaultValue="abc" style={{ width: 120 }}>
<Select.Option value="abc">抖音</Select.Option>
<Select.Option value="ulikecam">轻颜相机</Select.Option>
<Select.Option value="jianying" disabled>剪映</Select.Option>
<Select.Option value="xigua">西瓜视频</Select.Option>
</Select>

直接覆盖 Semi-UI 的 Select 还是单独弄个 XxxSelect?

方案虽然有了,但还有个细节上的事情需要我们考虑。目前业务中使用 Select 组件的方式是从 semi-ui 中引入来使用的。

1
import {Select} from 'semi-ui';

我们可以隐式的在编译过程中替换掉 semi-ui 中的 Select 组件,改为我们自己的实现。也可以显式的单独实现一个 XxxSelect 组件,让业务方有感知的引入。哪种更好呢?

从迁移成本上来说,直接使用 semi-ui 中的组件,业务代码不需要额外的改造,而单独的组件需要修改引入方式。

从使用体验上来说,表面上直接使用 semi-ui 中的组件用户的心智负担更小,但这严重依赖我们组件的实现质量,一旦组件内部实现出现了问题,业务开发同学可能意识不到我们进行了替换,他用的已经不是 semi-ui 中的 Select 组件了,排查问题相对较难。

你会选择哪种?