React 根据状态动态化功能的一些思考
本文最后更新于:2021年2月22日 下午
场景
之前吾辈也在 SF 上询问过 类似的问题。
目前在实际业务中遇到了两种情况
- 程序的功能在分发给不同组织使用的时候有所差异,即不同的组织都会存在一些定制化的功能。
最常见的差异例如- 表单的字段存在差异
- 列表展示的字段与相关操作有所不同
- 组件内的代码在某个权限下才会执行,但是又依赖于组件内的一些状态,如何将这些代码分割到不同的地方(例如不同的文件)便于之后的维护。
- 一些按钮在指定权限下存在
- 一些数据在指定权限下展示
方案
- 使用动态配置渲染不同的页面(可序列化的配置)
- 根据状态匹配不同的动态组件
- 使用 hooks 封装不同的逻辑
- 使用状态图控制状态和逻辑
实际调研结果
使用动态配置渲染不同的页面
实际上,之前有看过吾辈写的 react 通用列表组件封装 就知道,实际上列表已经被配置化了,可以使用配置的形式去渲染一个完整的列表页面,因此可以根据不同的组织使用不同的配置就好了。但是,事实上并没有这么简单,因为就算是简单的列表,也仍然包含上下文,而这,正是配置不能拿到的内容。
上下文主要包括
- 需要异步请求的数据,例如下拉框的选择项
- 需要从路由上获取的数据,例如搜索条件
- 需要对页面内的其它组件进行操作时,例如点击按钮有个新增列表项的弹窗
可以有几种解决方案
- 通过函数,而不是单纯的配置,这样,可以通过参数解决一些上下文的依赖情况
- 通过函数且异步,可以解决 api 请求时,此时的 api 必定是可以用的,但是会依赖于 api。
但这仍然会带来问题
- 数据不再纯粹,无法序列化。
- 不同配置依赖的数据可能不同,需要配置自己去解决,那么如果这样想的话,那么配置就需要自行获取数据,而不是外部传递数据了
- 仍然无法使用状态
- 最重要的是,使用函数之后变得不再像是配置了
根据状态匹配不同的动态组件
- 配置更为灵活,能够获取到组件的上下文
- 接口请求也没有问题
- 对不同配置,可以自行对数据进行处理
问题
- 无法如同纯数据配置那样,复用逻辑这么彻底,但是也可以通过 hooks 解决。
- UI 复用问题
先使用组件的方式编写一下,看具体结果如何 - 无法序列化也意味着无法放到后端,甚至意味着很难做动态加载
使用
- 使用一个 wrapper 组件来讲 UI 和通用逻辑给包裹进去
- 使用另外一套组件去区分不同租户的配置(因为是在组件内部写配置,所以该配置可以灵活的使用任意接口,组件上下文可能还不太行)也就是用多个组件来解决这个问题。
可以再尝试一下有没有解决方案。
使用 hooks 封装不同的代码
- 相比于处理 是哪一个,更适合处理 有或没有 的代码分割
- 能够使用 react 的状态
问题
- 使用 hooks 必须放在函数组件最顶层,导致本质上无法
lazy
加载。参考:Hook 规则 - 使用 hooks 同样难以序列化存储到后端
使用状态图控制状态和逻辑
使用 hooks 封装代码最适合处理元素级的权限控制,但在面对需要根据多个维度的状态决定程序的状态或行为时,就有点力不从心了。而这,也是为什么有限状态机为什么有用的原因。
结论
最终,我们选择了最灵活的 动态组件 + Hooks 共享逻辑 的形式,虽然使用动态组件会增加一些冗余度,但也可以通过子组件或 hooks 的形式复用逻辑,实际上在工程化减小的复杂度的收益是要高于代码冗余的。
使用示例
登记相关内容已经使用该方式进行了重构
src/pages/register
common
: 通用的一些组件和逻辑,例如请求后台接口应该是统一的,但返回的数据类型却应该是单独的form
: 表单相关组件,提供给列表/详情页面使用detail
: 详情页面list
: 列表页面
organizations
: 不同组织的目录org1
: 组织 1org2
: 组织 2
吾辈编写了一个简单的示例,代码在 dynamic_state
其他技术问题
- 如何在运行时根据组织切换功能
- 可以再包一层组件而非简单的从
lazy component map
取出组件
- 可以再包一层组件而非简单的从
- 如何在运行时添加新组织的功能
- 可能需要插件的实现方式,支持动态加载进来,例如 vscode 的插件体系。
- 如何使用 hooks 更好的复用逻辑
- 使用 hooks 封装逻辑,使用小型组件封装 UI/UX
- 如何在打包阶段干掉不相关组织的代码
- 需要修改 webpack 相关的内容,目前不予考虑
React 根据状态动态化功能的一些思考
https://blog.rxliuli.com/p/6be33848912b4a63bd0bfebe0e99fda1/