组件库开发小结

背景

从一月初回公司到五一又开始请长假这四个月实习时间以来,我估计得有三分之一的时间是在写组件库的,所以写一篇 Blog 简要谈谈我对组件库的认识,和在这方面的实践。

我对组件库的认识

三大框架有一个共同点,就是组件化思想,把一个页面划分为多个组件,从一个个组件写起,最后组装成一个页面,乃至整个项目。而在这一个个业务组件里,我们或多或少都会使用到基础组件,或者叫做 UI 组件,就是只负责 UI 和自身逻辑的一些组件。比如一个 Input 输入框组件,它负责了基本的 UI 展示,包括各种 hover、focus、disabled 态,输入内容校验、以及对输入框内容变化、按下回车、点击清除图标时的回调函数进行封装等自身逻辑。这样在我们开发时就可以只专注于业务逻辑代码,而不需要再去重新写输入框的样式和基本逻辑。

可以说,维护自己的一个组件库的好处有:

  1. 将 UI 和业务逻辑分离开来,使业务方只需关注业务逻辑。

  2. 组件复用。一个项目中可能有很多个页面都使用到了输入框,通过组件库可以很方便地复用这些基础组件,大大提升了开发效率。

  3. 样式统一。因为基础组件都是在组件库中维护的,所以可以保证不同页面的同一个基础组件样式是一样的,不用在各个使用到的地方都维护一份组件样式。

  4. 可维护性高。通过组件库引入基础组件,使得业务组件代码量大大缩短,降低了耦合性和复杂度。并且基础组件都统一在组件库中管理,修改起来也很方便。

为什么要维护自己的组件库

前端估计是最喜欢造轮子的了,不说各个框架,只说组件库就有数不胜数个开源的。有的直接从零开始搭建一个组件库,有的则在已有的组件库的基础上进行二度封装,形成自己的组件库。那么,为啥要再搞一个自己的组件库呢?直接套用 Antd 或 ElementUI 等稳定又好使的组件库不好吗?我觉得主要原因是:每个项目都有自己的设计规范,像前端开发,基本上都会由 UI 设计师先出设计稿,具体给出每个页面的布局结构、每个地方的宽高、间距、字体、颜色等信息,再由前端编码还原设计稿。而像 Antd 等组件库,它的组件样式都是按它的设计规范来的,很可能并不适合于我们自己的项目。而要修改它的样式的话,就得去写全局的样式,来覆盖掉 Antd 的样式,这样可维护性不高。

如何基于 Antd 再度封装组件库

以下提到的 Antd 特指 3.22 版本。

既然只是修改 Antd 组件的样式而已,所以可以先新写一个组件,在这个新组件内部引用 Antd 组件,从而复用它的逻辑。

import { Input as AntdInput } from 'antd';
import { InputProps as AntdInputProps } from 'antd/es/input';

function MyInput(props: AntdInputProps) {
  const newProps = {
    // prefixCls 用来标志组件的类名前缀,后续会说到
    prefixCls: 'dangosky-input',
    ...props
  };

  return <AntdInput {...newProps} />;
}

// 还得根据组件判断是否需要转发 Ref
export default MyInput;

在上面这个例子中,我们就已经实现了再度封装 Antd 的 Input 组件。通过观察 Antd 组件的 DOM 结构可以发现,所有的 Antd 组件都有一个统一的规范,它的类名都有一个固定的前缀,也就是 ant-{组件名},以此来标志每一个组件。为了和 Antd 做区别以及维护方便,我们可以统一用 dangosky(是团子的天空幻想的意思呐~)来标志我们这个新的组件库。具体表现是:

  1. 使用 dangosky 来替代 ant 作为每个组件的类名前缀。

  2. 通过配置后可以使用 import { Input } from 'dangosky' 来导入我们的 dangosky 组件库,使得项目使用的组件库从 Antd 迁移到 dangosky 只需要修改它的导入包名就行,实现低成本迁移。

下面再说说基于 Antd 再度封装的组件库实现原理。因为只是对 Antd 的样式修改而已,所以主要就是修改组件的 className 类名、修改 less 样式文件中的类名:

1. 修改组件的类名

Antd 为每个组件的 props 都提供了一个 prefixCls 属性,用来作为每个组件的类名前缀。所以可以在我们的新组件中指定 prefixCls,将其替换成我们自己的 dangosky。这样组件中的 className 类名就可以从 ant-input 变成 .dangosky-input 了。

Antd 有一个 getPrefixCls 函数来处理这个逻辑,它是由外层的 configProvider 组件利用 React 的 Context 特性传递到内部组件的,可以无视组件嵌套的深度。

以 input 组件为例,Antd 内部是这样调用的。如果我们在组件内指定了 prefixCls 这个 prop 的话(它重命名为了 customizePrefixCls),getPrefixCls 就会返回我们指定的类名前缀,否则就默认返回 ant-{组件名}

2. 修改样式文件的类名

将 DOM 节点的类名前缀改为 dangosky 后,还需要将 less 中的类名前缀也改为 dangosky 才行,不然会匹配不到相应的样式。Antd 里有一个全局的样式文件,包含了一些全局的样式变量,它会在每个组件中都被引入进去。其中有一个变量是 @ant-prefix ,less 中的每个类名都会在前面加上 @ant-prefix 。所以我们可以定制新组件库的主题文件,在里面将 @ant-prefix 值修改成 dangosky,之后在每个新组件的样式文件中导入这个主题文件覆盖原来的 Antd 值就可以了。

3. 在组件里自动引入样式文件

在使用 Antd 组件的时候可以不用手动 import 进样式文件,Antd 提供了一个插件 babel-plugin-import 来实现按需加载和自动引入样式文件。

我们在使用某个组件的时候,都是直接 import { xxx } from 'antd'。如果不加以额外的插件辅助的话,这样的写法会导入 antd 中的所有模块,如果要只加载指定的组件的话,我们就需要使用下面这种写法了。

import xxx from 'antd/es/xxx';
import 'antd/es/xxx/style';

这种写法的问题在于比较繁琐,那么有没有更简洁的方式来实现按需引入呢?我们可以使用 babel-plugin-import 插件,使用这个插件后,我们依旧可以使用 import { xxx } from 'antd' 来导入我们想要的组件,这个插件背后会帮我们转换成 antd/es/xxx。可见源码

babel-plugin-import 还会再自动导入进它的样式文件。从它的源码里可以看到,当在 babel 配置里设置了 style: true 的话,它会自动引入该组件下的 style 文件夹,所以就会默认引入我们写好的 index.tsx 文件(再在这里引入具体的样式文件)。除了取值为 true 外,还可以是 false 和一个函数,具体可以看文档。


  转载请注明: DangoSky 组件库开发小结

 上一篇
Vue2 的 Diff 算法 Vue2 的 Diff 算法
文章中涉及到的 Vue 代码特指版本 2.6.11。 虚拟 DOM什么是虚拟 DOM要理解 Diff 算法,就得先理解好虚拟 DOM。虚拟 DOM 说白了其实就只是一个 JavaScript 对象,它抽象地描述了一个真实的 DOM 结
2020-05-07
下一篇 
颜色规范校验 Lint 颜色规范校验 Lint
背景最近有一个需求,要做的工作是编写一个校验脚本,在代码提 commit 的时候去校验提交的样式文件里有没有不符合规范的色值。项目里会有一个统一的 normal-colors.less 文件,样式文件里使用到的色值都必须使用这个 norm
2020-04-11
  目录