状态、编排与原子组件化的一切
前言
最近浅读了一下 Atomic Design,之前关于组件化如何编写如何进行的问题,也感到豁然开朗了。实际上一个网站就是由许多页面构成,页面是由许多大的有状态或无状态组件构成的,或者我们可以称其为对有状态或无状态组件的编排,有状态组件又是能够从许多不可再分的无状态的组件得来。
这篇文章就从最小不可分的单位,无状态组件开始探讨应该如何做组件化,或者你可能更喜欢叫它原子组件。
何为原子组件
探讨这个问题之前,我们先定义一下原子组件的两个特征:不可变性和不可分性。
不可变并不是说明只接受常量,而是说明它接受的 props 是不可变的,或者说它接受的 props 是原子类型的,因此原子组件无需再去关心状态变化,只需要关注每一次渲染的 props。
不可分是建立在已有的设计系统上的,你可能会喜欢使用 shadcn/ui 这样的组件或者是一堆带样式的 html 标签组件来编写你的组件,这些组件我称之为全局原子组件,因为无论是什么地方都可以自由地用到这些组件。相对来说,本文探讨的是局部原子组件,一个原子组件可以由另一个原子组件构成,我们不叫它分子组件,因为大多数时候再去拆分由一个或者两个原子组件构成的组件没有什么意义。
不可变性
从组件的 props 来看不可变性,props 决定了组件的感知,一个原子组件对应来说,应该也要感知到”原子”类型,这里的”原子”并不是指并发中的原子类型。诸如 string number boolean 等基本数据类型都可以叫原子类型,甚至 null undefined 也可以叫原子类型。
观察这些类型有个共通的地方,就是它们都是不可变的,或者说它们的值是不可变的,Immutable 在 Functional Programming 令人印象深刻。特别是 Rust Scala 中,前者关注的是不可变性带来的安全和性能提升,后者更倾向于一种编程范式的推崇。
不可变与无状态几乎是同一种东西,当一个组件绑定了带状态的值,那么就说明这个组件和状态耦合在了一起。状态可变,组件也就是可变的了。如果你有一堆耦合了状态的原子组件,那你在维护的时候会非常痛苦,因为你发现,当一个状态绑定了多个原子组件,修改一处状态就需要修改多处组件,最糟糕的是,如果你感知不到哪里需要修改,那么你可能就会迭代出一个 Bug。这也是老生常谈的代码异味中的”Shotgun Surgery”(散弹枪式修改),当你需要修改一个功能的时候,你需要修改很多地方,甚至你都不知道哪里需要修改了。
所以说,我们要推崇原子的,无状态的组件作为我们的基石,原子组件就是接受不可变数据的组件。
不可分性
原子组件的另一个特征就是不可分性。
举个例子,有一个组件库提供了 Text Card。显而易见,这两个组件一定是原子组件,假设在编写页面时候,需要一个 InfoCard 组件,这个组件由 Text Card 组成,也就短短的几行,InfoCard 也是无状态的前提下,这时候的拆分没有太大的意义,可以认为这个组件也是不可分的了,同时这个组件也可以在其他地方复用,所以我们认为这个组件也是原子组件。
状态
状态带来了风险,也带来了交互体验。
我们可以根据页面构成来把一个页面分为数个区域,每个区域都是有状态或是无状态的组件。如何获得状态?比起使用 useState,我还是更喜欢使用依赖注入的方法来管理我的状态,
编排
TODO