Replies: 2 comments
-
👍 |
Beta Was this translation helpful? Give feedback.
0 replies
-
抽离公共可复用组件一直是个难题,其实想要真的做到极致就意味着粒度要足够细,就算 antd 也有一些场景是不满足定制化需求的,所以要我说最好的方法就是在一个具体的业务中,和 UX 同学定制一套符合当前项目的 UI 组件,整个业务域相同或者相似的场景从 UX 层面就需要从这个库里面去做增删改查 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
在业务开发过程中,我们总是会期望某些功能一定程度的复用。很基础的那些元素,比如按钮,输入框,它们的使用方式都已经被大部分人熟知,但是一旦某块功能复杂起来,成为一种“业务组件”的时候,就会陷入一些很奇怪的境况,最初是期望抽出来的这块组件能有比较好的复用性,但是,可能当另外一个业务想要复用它的时候,往往遇到很多问题:
诸如此类,时常使人怀疑,在一个业务体系中,组件化到底应该如何去做?
本文试图围绕这个主题,给出一些可能的解决思路。
组件的实现
状态与渲染
通常,我们会有一些简单而通用的场景,需要处理状态的存放:
一般来说,我们有两种策略来实现,分别是状态外置和内置。
有状态组件:
无状态组件:
通常有状态组件可以位于更顶层,不受其他约束,而无状态组件则依赖于外部传入的状态与控制。有状态组件也可以在内部分成两层,一层专门处理状态,一层专门处理渲染,后者也是一个无状态组件。
一般来说,对于纯交互类组件,将最核心的状态外置通常是更好的策略,因为它的可组合性需求更强。
使用上下文管控依赖项
我们在实现一个相对复杂组件的时候,有可能面临一些外部依赖项。
比如说:
一般来说,我们给组件提供外置配置项的方式有这么几种:
这三种里面,我们需要尽可能避免直接引入全局依赖,举例来说,如果不刻意控制外部依赖,就会存在许多在组件中直接引用 request 的情况,比如说:
注意这里,我们一般意识不到直接 import 这个 request 有什么不对,但实际上,按照这个实现方式,我们可能在一个应用系统中,存在很多个直接依赖 request 的组件,它的典型后果有:
这个问题,可能有的研发团队中会选择先封装一下 request,然后再引入,这是可以消除这种问题的。
因此,要尽量避免直接引入全局性的依赖,哪怕它当前真的是某种全局,也要假定未来是可能变动的,包括但不限于:
需要尽可能把这些东西控制住,封装在某种上下文里,并且提供便利的使用方式:
这样,我们在整个大组件树上的视角就是:某一个子树往下,可以统一使用某种控制策略,这种策略在模块集成的时候会比较有用。
使用 Context,我们可以更好地表达整组的状态与操作,并且,当下层组件结构产生调整的时候,需要调整的数据连接关系较少(通常我们倾向于使用一些全局状态管理方案的原因也是这样)。
状态的可组合性
在实现组件的时候,我们往往发现它们之间存在很多共性,比如:
从更深的层次出发,我们可以意识到,几乎任意一个组件,它所使用的状态与控制能力都是由若干原子化的能力组合而出,这些原子能力可能是相关的,也可能是不相关的。
举例来说:
这样的一个组件,表达的就是对只读状态的读写操作。如果某个组件内部需要这么一些功能,可以选择直接将它组合进去。
更复杂的情况下,比如当我们想要表达这样一种特殊的表单卡片组,其主要功能包括:
分析其特征,发现来自几种互相不相关的原子交互:
它的实现就可能是这样:
由此,我们有可能在每个组件开发的时候,将其内部结构分解为若干独立原子交互的组合,在组件实现中,只是组合并且使用它们。
注意,有可能部分状态组之间存在组合顺序依赖关系,比如:“可选择”依赖于“列表”,必须被组合在它下层,这部分可以在另外的体系中进行约束。
分层复用
在业务中,组件的复用方式并不总是一样的。我们有可能需要:
每当我们需要设计一个“业务组件”的时候,就需要慎重考虑了。可以尝试询问自己一些问题:
比如说,一个内置了选择省市县的多级地址选择器,它就是这么一种“业务组件”。我们以此为例,尝试重新解构它的可复用性。
对于地址的查询,就是外部依赖。注意,尽管大部分情况下这个是不会改的,但是仍然存在这个可能性,需要提前考虑这类事情,通常,遇到有数据请求之类的东西,尽量去抽象一下。
如果需要建立另外一种选地址的组件,交互形态不同,但逻辑可以是一样的。
有可能被用来选择其他东西。
所以,回答了这些问题之后,我们就可以设计组件结构了:
业务上下文
交互上下文
在组件的实现中:
使用的时候:
在这个部分,总的原则是:
在细分实现中,再考虑两个部分分别由什么东西组合而成。
在一些比较复杂的场景下,状态结构也很复杂,需要管理来自不同信息源的数据。在某些实践中,选择将一切状态聚合到一个超大结构中,然后分别订阅,这当然是可行的,但是对维护就提高了一些难度。
通常,我们有机会把状态去做一些分组,最容易理解的分组方式就是将业务和交互隔离。这种思考方式可以让我们的关注点更聚焦:
多级子树
在很多时候,一整块复杂的业务交互包含的内容过多,涉及多个交互块的流转,或者存在比较复杂的数据共享关系,如果非要集中管理,维护起来会很难。
当前社区的技术方案,对这块是比较欠缺考虑的,绝大部分人采用的是两种比较极端的策略:
但是考虑到在一个业务体系中,有可能有的模块的组件树深度过大,交互过于复杂。又或者,项目之间的集成关系不是一成不变的,经常有单个项目整体下沉为被集成方。诸如此类的需求,会对状态逻辑、组件结构提出更多需求。
我们可以这样的策略:
整体结构形如:
这个体系下:
这样,实现的只关注于实现,集成的只关注于集成,两者的视角相对是分离的,主要的适配逻辑都集中在各自的适配器上。
状态的依赖关系
在 hooks 推出之前,React 中管理状态之间依赖关系的机制是有所欠缺的。以其他技术栈为例,往往提供了一种称为 computed 的机制,使得可以定义出一些无副作用的依赖计算链路,例如:
早期,React 体系只能额外借助类似 RxJS 这样的工具库来实现类似功能,在 hooks 和 Recoils 推出之后,有了更多选择。
当我们认为“组合若干个独立状态分组来实现组件,其灵活性更高”的同时,就需要面临一些将组合结果再次暴露出去的场景。在这样的场景下,有可能需要对状态依赖关系的隐式或者显式表达。
组合状态提供了一种视角:从使用者的角度看待状态数据的来源和变换关系。这对于复杂场景下,追踪状态的变化链路来说,非常有用。我们可以对于视图上每一个状态,都追溯到它是由什么业务状态所关联计算的交互状态,从而在跟踪问题的时候,能以最快的方式定位到问题。
它的视角是:
此外,以这种视角出发,还有机会把一些动态的业务计算规则通过注入的方式加进来,类似 Excel 里面的一些公式,从而更容易支持业务上的一些配置化需求。
在开发过程中,也要注意尽量以状态驱动的视角去解决问题,尽可能少用 ref 去获取“组件引用”。
工程链路
除了常规的组件化生产链路,还可以关注另外一些工程方面的视角。
组件依赖形态
前端组件的发布方式也是值得考虑的,与早期静态的前端工程链路不同,组件的依赖存在两种不同的方式:
这两者的使用方式有很大不同。
以包形态依赖的组件,其构建与发布链路是跟随主应用的,主应用与它们是比较强的耦合关系,会需要在代码结构、交互呈现方面,都结合得更紧密一些。
以服务方式依赖的组件,有单独的构建与发布链路,主引用与它们是松散耦合关系,一般来说,会采用某种微前端方案来集成它们。
这两者在业务上都是可能出现的,需要从业务集成关系的角度来判断。
在一个相对可控的体系中,建设组件依赖体系的时候,需要多考虑一些其他环节,比如依赖的反向管控。所谓依赖关系的反向管控,是指,从一个组件出发,知道依赖它的有哪些组件或者产品。通常,在以服务方式集成的组件上,这一点非常重要,否则,被多个业务依赖的组件服务要单独发版了,可能影响的范围都难以精确定位。
这个部分的方法论可以参照其他体系,比如后端的服务依赖监控策略去建设。
跨技术栈集成
当前,前端技术栈的分化比较严重,对于行业软件公司,这样的情况尤其严重,因为产品周期都更长。实时翻新所有组件是不现实的,因此,我们需要寻求更通用、更长远的集成方案。
当前主流的前端框架都是数据驱动,而技术栈集成的组合是可以穷举的,比如说,我们可以有:
类似这样,就可以不必过于强求组件自身的实现方式。
业务组件的使用方式变成:
整体来看,一个应用可能是一个比较复合的组合:
整个这块,就是“前端微服务”,但是在不同场景下,存在不一样的实现策略。一般来说,如果对所有被集成方的生产过程能够有一定约束,整体实现就可以比较好一些。
需要注意的是,当前一些“微前端方案”侧重于解决的一部分场景是历史遗留问题,或者是对生产者缺乏有力约束的场景,如果是整个应用都处于可控范围,异构框架的集成就相对比较友好一些,有机会做得更好。
如果我们能够把状态管理与交互实现隔离得比较好,甚至很容易做出技术栈中立的状态管理方案,并且能够更好地隔离 UI 框架可能带来的影响。
总的来说,从交互和产品角度看,优先期望能有完整的交互集,但具体组件实现允许有异构方案。
测试与分析
一般的业务团队中,前端自动化测试都是一个基本无法推进的事情,主要原因是逻辑和状态过于分散,覆盖所有情况的自动化测试用例,数量可能庞大到超过想象,并且,每次需求变更,需要变动的测试也非常多。
但是,在合适的方法论下,这个事情也不是完全无解的。我们需要尽量去做到交互与业务逻辑的隔离,当组合关系比较清晰的时候,业务和交互是可以分别测试的。
在测试业务的时候,交互细节可以忽略,例如,我们在测试一个使用表格承载的业务的时候,可以检验它的数据结果始终满足某种形态的对象数组就可以了,无需关注是否正确显示为表格(这是另外一个问题)。
甚至,我们有机会造出一组专门用于测试的渲染器,专门用来配合业务测试。
此外,需要注意到,我们之前的整个探讨,都在强调一个理念:业务与交互隔离。在隔离到比较好的情况下,把交互全部视为黑盒,就可以得到很纯净的业务形态,据此,有机会去做到基于状态组合的语义化业务埋点。
小结
总的来说,组件开发的方法论可能是相对中立和普适的,但组件库的整体建设方案,与所在的行业有不小的关系。如果是从事行业软件领域,对交互集的掌控就是非常重要的事情。
考虑方案的时候,如果优先从产品的集成关系角度出发看待问题,有可能是比较好的,它至少保证业务的可用性尽可能不被技术方案限制。
本文述及的一些策略,从另外一些视角看,可能有另外一些认知。比如说,在提到管控依赖项的策略中,如果把“基础组件”也视为是一种可注入的能力,那整个业务部分就可以变成另外一种奇特的形态:类似某种“小程序”体系。
篇幅所限,本文所提及的都是很初步的内容,更多细节需要单独展开。
Beta Was this translation helpful? Give feedback.
All reactions