聊一聊--视图层 React 组件的层级设计

2,856 阅读5分钟

自己在最近的开发工作中,面对一些可能很多前端同学都会遇到的问题,有了一些心得体会。

所以有了这篇文章,想来和大家聊一聊该如何设计和抽象我们的 React 组件。

React 体系

首先来简单说一下我眼中的 React 体系

我认为使用 React 的前端应用基本是由两部分组成:业务架构 以及 视图组件

我之所以认为 React 体系下的前端应用分为这两部分,是因为

React 是擅长视图交互开发,而不擅长复杂业务实现的

如果业务系统与视图层过度耦合,那么对于大型复杂应用的可持续维护将是一个噩梦

业务架构

我这里的 业务架构 是指应用中的

应用分层,业务逻辑,数据模块等,简而言之就是非视图展示的那一部分

那这一部分是具有很高的灵活性,在不同的设计者,在不同的业务面前,千变万化,甚至是根据业务的发展在不断的更新调整的

不过这一部分的内容并不是这篇文章想讨论的重点内通

视图组件

视图组件,即我们使用 React 来表现视图层的这一部分。

关于这一部分,我不是想要说一个React组件该怎么写,而是想谈谈一个在视图层中再去分层的一个设想

那这个“分层”的目标就是 组件复用,业务隔离

React 组件的层级设计

这一部分是这篇文章的重点内容,主要以一个例子来讲一下我的眼中,如何通过分层的手段,达到上述提到的目标

首先,我把 React组件 分为两种类型:纯UI组件 以及 业务视图组件

纯UI组件

纯UI组件就是相当于我们使用的很多样式组件库


它们最重要的特点就是:它们和业务没有一毛钱关系。

它们的 props.value 是 string 类型,而不是某个枚举值;它们的交互行为叫做 onClick 而不是 onClickProduct。


它们的设计理念维持某个功能不可拆的原子性(从实际业务角度看,某个组合不会在拆,我也认为是符合这一原则的,没有追求无意义极致的必要)。


它们单一职责的,它们所负责的东西足够的“简单”。当我使用它们的时候是可以灵活地将其组合,而不是嫌弃它过度 “膨胀” 了。

业务视图组件

我眼中,业务视图组件就像是一个连接者的角色:

  • 对于业务模块和数据模块来讲,它要从中承接数据和业务;
  • 对于视图层,它要将承接的数据根据业务转换成要展示的视图数据,组合纯UI组件来展示它们

其实 业务视图组件 如果复杂的话,又可分为两部分:通用业务的视图组件 以及 业务隔离的视图组件


通用业务的视图组件

对于通用业务视图组件来讲,要把控好它的通用范围,不要让他又变成一个看起来好用的“巨无霸”


那么什么是通用呢?

举个例子:

假如 A1,A2,A3 三个组件,它们 Props 分别有 10 个参数,其中 8 个是一致的,2 个是有差异的


那么如果它们被设计成了一个 “通用组件” A ,那它将拥有 14 个参数:

8个通用必选,6个不通用可选

此时 A 有将近一半的参数是可选,那它还是足够 “通用” 吗?!!

如果有一个其他同学接手 A ,他又如何能够从组件的接口设计中快速的理解组件,理解业务呢?


但是那按照现在 “敏捷” 的开发模式,第一期开发了 A1 ,第二期开发 A2 时,只加两个参数,你会选择把 A1 改成 A ,还是拆成 A1、A2 两个组件呢??

我觉得大多数人恐怕都不会选择第二种,直到开发 A4,A5,A6 时感受到痛苦


所以我认为合适的做法是:

将 A1 ,A2 ,A3 中通用的业务,抽离成通用的组件 A;并在 A1, A2, A3 组件体现业务差异,并且互不影响。

业务隔离的视图组件

抽离出 A 组件后的 A1,A2,A3 即为业务隔离组件。


你不会在 A1 中看到为了兼容 A2,有一个A2的业务可选参数;

也不会看到 A1 中有一段处理 A2 的业务逻辑。

伪代码

所以我认为合理的拆解后,代码的样子应该是下面这样的:

interface BaseProps {
    // 8 个通用参数
}

class A extends React.Component<BaseProps> {
    render() {
        // ....   
    }
}

interface A1Props extends BaseProps {
    // 2 个业务参数   
}

class A1 extends React.Component {
    render() {
        <TopBar value={this.prop.value} />
        <A {...this.props}>   
    }
}

connect(() => { data: a1.data }, A1)


interface A2Props extends BaseProps {
    // 2 个业务参数   
}

class A2 extends React.Component {
    render() {
        <Search onSearch={this.onSearch} />
        <A {...this.props}>   
    }
}

connect(() => { data: a2.data }, A2)


最后,我想简单的总结一下:

在日常的业务开发中,UI可能是最不稳定的一块。

那如何在这个“不稳定”中尝试去找到合理的,有效率的解决方式,去让它相对的“稳定”下来,应该也是一个值得我们去思考,探索的一个方向吧