Skip to content

从一次 CR 谈业务项目中的组件分层与复用 #68

@CommanderXL

Description

@CommanderXL

本文章写于 2023.10

问题&背景

最近在CR的过程中暴露一些组件复用困难的问题,遇到这些问题一个粗暴的解决方案就是 copy 代码,然后在这个基础上做一些改动后使用进而出现代码冗余的情况:

  • progress (6次)
  • biz-fixed-bottom-btn (4次)

一般我们拿到设计稿进入实际的开发工作之前我们都会尝试对页面进行拆分,这个拆分的最初目的可能很简单:提高代码可读性,明确页面/组件结构关系、减少页面/组件维护成本。将原本庞大的业务逻辑拆分到每个组件当中去处理。

但是随着业务产品的迭代,慢慢的出现一些一眼看上去是同一类的组件。仅仅做基本的组件拆分,而缺少一定程度的设计,当面对重复出现的视图、功能逻辑,我们在第一阶段做的拆分工作就比较难以支撑这些同类需求的迭代,进而也就会遇到前文提到的组件复用困难,需要通过 copy 改造代码的方式来实现需求。

再回到上文提到的CR问题,为什么同一类型的组件出现了这么多次但是复用起来很困难?

先分析下组件的实现:

首先对于页面/组件做了结构上的拆分,将原本放到大组件当中处理的逻辑挪到了组件内部。对于拆分后的组件而言,接受一个大的业务 props,同时将业务逻辑、组件自身的状态逻辑、视图渲染都杂糅到组件内部,这三部分的内容在组件内部相互作用进而决定最终的视图呈现:

component-logic-flow1

正是因为这三者之间的依赖关系使得同一类的业务场景想要复用这个组件的成本变的很高:不管是哪部分的代码逻辑需要定制处理(后端的字段不一致,取值逻辑不一致等)都会使得原本的代码变得越来越难维护,索性将原来的组件单独 copy 一份出来做一些差异性的改造快速满足业务的开发诉求,同时还不会影响到原有的组件代码。

那么针对这种业务组件的开发有哪些“套路”可以有效的降低业务组件内部的复杂逻辑依赖关系,同时也能满足复用的场景呢?

在此之前我们先来看下司机前端侧的一些业务现状

业务现状

目前团队所负责的业务方向主要包含xx等5个业务方向,每个业务方向还会有不同的品类:品类a/品类b等。每个业务方向(产品)基本都有属于自己的“设计语言”:样式结构/风格(主题),功能(业务表达),自成体系。一般跨业务方向、品类的组件复用起来还是有一定的改造成本的。

component-level-1

在所有这些项目的开发过程中有这么一层组件间的层次关系:

  • 业务组件:Component-A;
  • 基础业务组件:Biz-ui;
  • 基础组件:Cube-ui;

业务组件一般来源于某一个具体的产品功能,用以解决特定问题域,更加强调关注点分离,代码的维护成本;
基础业务组件一般来源于某一类的业务产品,在特定的业务背景下抽象的比较通用的,较少糅合业务逻辑,介于基础组件和业务组件之间,强调解决效率,可复用性,可维护性;
基础组件一般来源于我们设计交互元语言,用以定义业务产品和用户的交互、反馈,更加强调一致性,效率,可复用性;

底层的基础组件(可组合性、可拓展性、稳定性)是为了更好的服务于上层的(基础)业务组件。

此外从底层的基础组件到业务组件(由下至上),组件的业务属性越来越强,所要解决的问题域更加聚焦,更贴合具体的问题和场景,同时它们的可复用性也随之降低。

我们清楚不同组件之间的层级关系和定位后,再回到前文提到的CR当中所遇到的业务组件复用性问题。

这个时候我们已经有了可以满足特定场景的业务组件,现在因为有越来越多的场景都期望复用这个特定场景的业务组件。从上面的组件层级关系图可以看到:

从顶层的业务组件到最下面的基础组件(由上至下)是一个去业务属性的过程,解决的问题域也可以更加泛化。随着业务属性的剥离,组件的可复用性也可以得到加强。

component-logic-flow-2

经过对组件的改造:业务逻辑、组件自身的状态逻辑以及视图的渲染这三者间的逻辑扭转变的清晰(单向)。组件本身更加聚焦到组件自身状态逻辑和视图渲染,组件的视图渲染由业务逻辑驱动组件自身状态的更新来完成的,同时对于用户的事件响应经由组件的事件通讯和外层业务逻辑进行交互。经过改造后的组件更容易被同一类业务场景的不同业务逻辑驱动使用,因为这个时候不同的业务逻辑更关注的是组件标准化后的 props,而不需要过多关注组件的内部实现。

简单总结下改造的过程:

  1. 回归组件本身的结构、样式、行为;
  2. 拆分并剥离业务逻辑,收敛组件自身状态逻辑;
  3. 标准化组件通讯(props,事件);

上面的这个过程其实也是一个组件分层的过程,将原本只能满足特定场景的业务组件经过一定的改造能满足不同业务场景的复用。

组件分层

  1. 定义:一定逻辑设计的组件拆分;
  2. 可以解决的问题:降低组件的复杂度、明确组件之间的关系和边界、关注点分离、减轻组件的维护成本;

在做具体的组件分层工作的时候有几个问题需要考虑下:

  • 组件自身的定位

哪些组件可以作为基础组件抽象出来,哪些组件是为了满足同一类的业务场景诉求而抽离出来。

  • 组件间的交互和通讯

随着业务组件向下分层,组件不再是简单的接受一个大的业务 props,组件本身会更加聚焦自身的状态逻辑+视图逻辑,将原本业务组件糅合的业务逻辑剥离出去后,会由 props 去驱动组件自身的状态和视图的更新。

  • 复杂/冗余(过度设计)&效率之间的取舍

组件拆分的层级过深也会导致组件间的通信变的复杂(不管是 props 透传,还是事件通讯),心智负担变重;(一般组件间的层级关系>=3的时候就需要关注)

当然组件的分层和复用是有一定关系的:组件分层没做好,复用就很困难。

组件的复用

复用可以解决的问题:减少组件重复开发,提高开发效率;(减少业务属性 -> 回归组件本身的结构/功能定义)

当然在设计组件过程中也需要考虑一些问题:

  • 稳定性(组合):组件改动的影响面

一般而言越是基础、底层的组件,越是稳定,因为组件设计之初关注的是组件自身的结构和功能逻辑定义,这些是很少发生变化的,而多变的业务侧逻辑处理都交由外层去处理了。对于使用方来说是面向组件剥离业务属性后的标准化 props 的使用。

  • 可拓展性(组合):针对组件的定制化的处理

如果是隶属于组件本身的结构样式、行为的拓展这种情况下是可作为组件自身的能力增强而收敛到组件内部来实现的。如果是一些复杂的异形业务场景可通过开放 slot 去交由外层去处理。

此外在样式和结果的设计上可以利用更加现代的 Css Variables 能力去做主题定制。


组件库的复用

上面是通过一个具体的业务组件来看组件的分层和复用,下面再举一个组件库级别的复用的例子。在上文中提到了当前业务场景因为不同业务方向对应的“设计语言”自成体系,随着业务的发展,由网约车演化出了xx、yy等不同品类的业务,同时从去年也融合了zz品类。

在网约车演化xx、yy等品类的过程中,因为业务安全、时间成本等原因,最开始我们是将整个基础技术体系都 fork 一份单独给到对应的业务产品去使用和迭代,包括组件体系。这种 fork 模式确实非常快速的支撑了新的业务产品从0-1的快速迭代上线:只需要把差异化的内容单独修改就能使用。但是随着业务的发展和这些基础技术能力的更新迭代,这种 fork 单独迭代的模式的弊端逐渐被放大:基础技术体系能力的更新迭代需要被同时更改多次。

去年zz品类开始融合进来,对于我们来说是一次非常好的契机去做基础能力的重构升级来解决复用的问题。就拿组件库来说,不在单独 fork 一份代码去迭代,而是基于网约车的组件体系去建设:整体的组件分层没有什么变化,仍然是最底层的基础组件 (mpx-)cube-ui 服务好上层的(基础)业务组件,在这个过程中核心要解决就是基础组件和基础业务组件如何能更好的复用。

在上文中提到的组件层级关系当中,越是底层的组件它们所包含的业务属性也就越少,复用起来的成本也就越低。那么具体到单个组件,往往是组件本身的行为逻辑没有太多差异化的处理,而表现层的样式主题会有比较大的变化

针对表现层的差异,通过规范化模板结构及样式变量的拆分,样式变量采用了编译(预编译器变量)+运行时(Css Variables)的方案来提高组件的复用性。最终使得组件体系可以用一套代码去很好的支持不同品类和业务产品(网约车、xx、yy等)。(具体可参照 mpx-cube-uidriver-biz-ui 的主题相关的代码)。因为篇幅原因,有关组件库的主题定制能力就不在这里再去展开阐述了。

component-level-2


以上,通过同一类业务场景中的单个组件的复用和组件库支持跨品类复用场景来梳理了下组件分层和复用的底层逻辑,核心还是要深入分析具体的业务场景和诉求明确它们的差异点和共性,同时又能跳出单个业务场景站在全局看这一类业务场景当中涉及到的问题有什么更加合适的方案。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions