当我们在谈论react组件封装时我们究竟在谈论什么
经验
以下以react 组件为例,给出一些建议:
关于组件分割:
1,页面里看上去像个组件的局部,就应该是个组件。看上去不像组件的局部,也未必不应该是组件,大部分时候,分割组件用力过猛,比分割不足强。
2,组件是树形分级的,不是一个局部封装成了组件,它内部就不需要再分组件了,要层层细分。
3,凡是“肚子”里内容有可能变化的区域,都应该抽取成与内容无关的容器组件,包括不限于弹框,卡片,下拉,message。
4,学习组件分割的捷径是仔细看 ant design 是怎么做的。看明白了再找另一个组件库看,通过他们的异同揣摩其设计思想。毕竟,模仿比自己瞎琢磨高效的多。
关于组件接口:
1,组件接口应该是尽量抽象的,而不应该过分具体。比如一个代表 logo 的prop,就不宜假设 logo 一定是一个 http 开头的 url,因为实际使用完全有可能是一串 base64 字符串,或者一个 image 组件实例。
2,与上一条有点相关,组件 prop 应当具备重载性,比如凡是接收正则表达式的 prop,都应该同时允许接收一个js函数,从而支持高度定制。
3,如果你不确切知道什么时候用非受控组件,你应该永远封装受控组件,直到你非常清楚什么情况下受控组件不好用为止。
4,组件的 prop 应该具备统一的语义,比如 onClick 应该永远是我们熟知的那个意思,并且它接受的实参类型也应该符合直觉。
5,组件的 prop 应该具备统一范式,比如所有的回调类 prop 都应该有一个什么都不做的函数作为默认值。再比如如果一个组件的文案类 prop(比如 title)不支持多语言,那么所有组件的所有文案类 prop 都应该忽略多语言问题。
6,学习组件接口的捷径其实也是看开源组件库,但我不认为 ant design 是个特别好的学习对象,一方面是其中个别组件的接口设计我其实也不太认同,另一方面antd考虑的场景超级多,初学者可能不太容易 get 到它的设计思想。建议找简单一点的组件库学学。
关于组件实现:
1,不要不打草稿闷着头写实现,那样会把组件写成一坨屎,实际上组件的实现技巧往往比接口设计这类属于“架构师”的工作更复杂,更需要用心。
2,组件的状态模型与组件的 ui 展现应该分离。具体到 react 实现的话,你不应该草率的使用 set State 或者 hooks 去实现组件内部逻辑,而应该是先在纸面上设计好组件的状态机,用代码写出与具体 ui 甚至具体框架无关的实现,再基于它去展现 ui。前者现在有个名字叫 headless component。
3,绝大部分组件,不管是组件库中的组件还是业务项目中的组件,都不该引用全局数据流。
4,请务必重视组件的渲染次数问题,你的组件的渲染次数可能比你以为的要多的多。
6,如果是 vue 我会建议阅读 element 源码,但是 react 我不是很推荐阅读 antd 源码,因为我感觉 antd 的实现代码比 element 复杂不少。真想学习实现的话,反而是看vue框架下的组件库源码更好些,反正思想都是想通的。
关于组件发布:
1,通用组件的版本号,请严格遵守 semver 规则,否则会给人带来很多麻烦。
2,组件库建议使用 pnpm mono repo 模式管理,每个组件作为一个 npm 包,不建议发那种所有组件在一起的巨大 npm 包。
3,但是在 cdn 上发布组件库 bundle 包,以便 html 直接引用,还是很有必要的。
4,通用组件请务必编写和发布比较详细的文档。
关于样式:
1,通用组件建议使用 less 等 css 前处理语言编写样式,业务项目中的组件建议使用 css modules 编写样式。
2,所有组件都应该支持自定义classname,从而给自定义样式保留最后的机会。
3,编写组件样式时请尽量不要假设外部 dom 节点的样式是怎么写的,而是尽量写高兼容性的样式。
4,不要改全局样式!
其他:
1,请尽量让组件的名字符合主流命名方式。如果是业务强相关的组件,也请统一命名规范,别一会 xxx-view,一会儿 xxx-form,不符合直觉的命名对使用者来说会造成痛苦。
2,建议在开发组件之前,想清楚你做这个组件的目标是什么,在哪些场景下使用,不要试图做那种“万能”的组件,应该约束其职责,当然,更不建议写个仿佛能用的组件就拿去凑合用,对于你最初的目标,还是一步到位为好,毕竟底层的东西改动上层应用很难不动。
3,建议关注组件的单测覆盖率。哪怕是业务强相关的组件,只要跨项目复用,造成生产事故的可能性就会陡增,因为你不可能指望业务前端还有单测或者完善的e2e test。
4,还是要正视通用组件的开发难度,绝大多数时候,你都不应该尝试去开发一个完全与业务无关的通用组件,投产比真的很低。自研组件库更是深坑中的深坑,新进前端 leader 切勿冲动。
考虑到复用性
为了提高组件的灵活性/复用性,那么所封装的组件,内部的一些内容是不能写死的,需要调用组件的时候传递进来才可!我们可以基于这样几种方式:
-
数据信息:可以基于 props(属性)传递进来
-
HTML 结构信息:可以基于 props.children(插槽)传递进来
还可以基于 ref 等操作,获取子组件的实例,从而调用其实例上的属性和方法;或者配合 React.fowardRef 实现 ref 转发,从而获取子组件内部的元素等!