一、前言
设计一个组件库的样式最重要的就是一点就是样式的高复用性和低耦合度。在一个项目中每个组件会用大量的公共样式,为了确保组件在不同的场景、布局中的样式保持统一,减少样式干扰的可能性。组件样式的命名也是非常需要统一管理的,一个优秀的命名能够增加样式的可读性,组件库中的组件可能会嵌套使用,所以封装公共样式和统一命名规范由为重要,它也直接决定了组件的易用性和扩展性。
这篇文章我将详细的向你介绍 Element Plus 中样式是如何封装设计的。
二、组件样式设计思路
Element Plus 中使用了 BEM
来给组件样式的类名来命名,整个组件库中所共有的 sass
变量也是大致按照这个来命名的,统一命名规则,可以使整个组件库的样式架构复用性和拓展性变得更强。正是因为采用了 BEM
命名规则命名类名,所以在封装样式类名也需要遵循这个规则来封装。
Element Plus 在 packages\components
目录下创建一个 base
目录来导入 packages\theme-chalk
目录下的公共样式( root
)和一些基本公共样式,这个目录下packages\components\base\style\css.ts
的文件可以导入打包好的全局 css
样式,加快渲染速度。采用这种目录结果管理样式是为了分支管理,尽可能的降低各个项目之间的耦合度。而packages/components
目录下的组件可以引用 components/base
目录下的公共样式。
每个组件下都可以创建 style
文件夹来导入公共样式和组件自身的样式,组件还需要将打包好的 css
样式在 css.ts
文件下导入,这样可以提前加载出 css
样式,优化了性能。
三、组件库封装样式的具体实现
一)、类名 BEM
实现
我们再 packages\hooks
目录下创建一个 use-namespace
文件,在这个目录下来完成组件的命名规范函数封装。
const _bem = (
namespace: string,
block: string,
blockSuffix: string,
element: string,
modifier: string
) => {
let cls = `${namespace}-${block}`
if (blockSuffix) {
cls += `-${blockSuffix}`
}
if (element) {
cls += `__${element}`
}
if (modifier) {
cls += `--${modifier}`
}
return cls
}
namespace
: 命名空间,一般用于区分不同的模块。block
: BEM 中的块(Block),表示组件的名称。blockSuffix
: 块后缀(Block Suffix),通常用于进一步细化块的分类。element
: BEM 中的元素(Element),表示块内部的某个具体部分。modifier
: BEM 中的修饰符(Modifier),用于描述块或元素的状态或属性。
根据这命名函数的条件分支,我们可以再细分一些处理函数,可以使用更加方便再一些特定的情况下。我们将这三个条件进行组合,生成六个处理函数,因为有些组件需要设计某些字段是否存在,存在就添加上对应的样式,我们可以通过 is
来实现。 这些就已经包括了组件中可能会用到的 class
类名称。
export const useNamespace = (
block: string,
namespaceOverrides?: Ref<string | undefined>
) => {
const namespace = ref('fz')
/**
* 生成 BEM 格式的块级类名
* 示例:
* 如果 block 为 'button',调用 b() 将返回 'fz-button'
*/
const b = (blockSuffix = '') =>
_bem(namespace.value, block, blockSuffix, '', '')
/**
* 生成 BEM 格式的元素类名
* 示例:
* 如果 block 为 'button',调用 e('icon') 将返回 'fz-button__icon'
*/
const e = (element?: string) =>
element ? _bem(namespace.value, block, '', element, '') : ''
/**
* 生成 BEM 格式的修饰符类名
* 示例:
* 如果 block 为 'button',调用 m('disabled') 将返回 'fz-button--disabled'
*/
const m = (modifier?: string) =>
modifier ? _bem(namespace.value, block, '', '', modifier) : ''
/**
* 生成 BEM 格式的块级后缀和元素类名
* 示例:
* 如果 block 为 'button',调用 be('primary', 'icon') 将返回 'fz-button-primary__icon'
*/
const be = (blockSuffix?: string, element?: string) =>
blockSuffix && element
? _bem(namespace.value, block, blockSuffix, element, '')
: ''
/**
* 生成 BEM 格式的元素和修饰符类名
* 示例:
* 如果 block 为 'button',调用 em('icon', 'active') 将返回 'fz-button__icon--active'
*/
const em = (element?: string, modifier?: string) =>
element && modifier
? _bem(namespace.value, block, '', element, modifier)
: ''
/**
* 生成 BEM 格式的块级后缀和修饰符类名
* 示例:
* 如果 block 为 'button',调用 bm('primary', 'disabled') 将返回 'fz-button-primary--disabled'
*/
const bm = (blockSuffix?: string, modifier?: string) =>
blockSuffix && modifier
? _bem(namespace.value, block, blockSuffix, '', modifier)
: ''
/**
* 生成 BEM 格式的完整类名
* 示例:
* 如果 block 为 'button',调用 bem('primary', 'icon', 'active') 将返回 'fz-button-primary__icon--active'
*/
const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
blo