类名管理工具 classnames 与 bem
在组件开发中,必不可少的要做样式类命名和管理,这里分享一下 Vant
源码中的工具函数 bem
和 classnames
包来管理组件中的样式。
bem 工具函数
BEM: BEM是一种前端命名方法论,主要是针对CSS,意思是块(Block)、元素(Element)、修饰符(Modifier)的简写。这种命名方法让CSS便于统一团队开发规范和方便维护。
vant
命名示例: 以 __
连接 element
,以 --
连接 modifier
(遵循规范)
van-button van-button--success van-button--normal van-button--disabled
van-button__content
van-button__text
ant design
命名示例:: 都以 -
连接(简单粗暴)
ant-btn ant-btn-text ant-btn-dangerous
用法
// 前缀
const modName = 'xui-button'
const bem = createBEM(modName);
bem() // 'xui-button'
bem('text') // 'xui-button__text'
bem('text', 'disabled') // 'xui-button__text xui-button--disabled'
bem({ disabled: true }) // 'xui-button xui-button--disabled'
bem('text', ['disabled']) // 'xui-button__text xui-button__text--disabled'
bem('text', { disabled: true }) // 'xui-button__text button__text--disabled'
bem(['disabled', 'primary']) // 'xui-button button--disabled button--primary'
源码解析
export type Mod = string | { [key: string]: any };
export type Mods = Mod | Mod[];
function genBem(name: string, mods?: Mods): string {
// 没有 mod 或 mod为false 则返回空
if (!mods) {
return '';
}
// 如果 mod 是字符串,则拼接上 blockName 返回,如 button--disabled
if (typeof mods === 'string') {
return ` ${name}--${mods}`;
}
// 如果是数组如 ['disabled', 'primary'], 则返回 ' button--disabled button--primary'
// 如果是数组如 ['disabled', undefined, 0, ''], 则返回 ' button--disabled'
// reduce 递归处理每个数组元素
if (Array.isArray(mods)) {
return mods.reduce<string>((ret, item) => ret + genBem(name, item), '');
}
// 如果是对象 { disabled: true }, 则返回 ' button--disabled'
// 循环对象每一个key,判断 value 是否为真,为真则递归处理 key
return Object.keys(mods).reduce(
(ret, key) => ret + (mods[key] ? genBem(name, key) : ''),
''
);
}
/**
* bem helper
* b() // 'button'
* b('text') // 'button__text'
* b({ disabled }) // 'button button--disabled'
* b('text', { disabled }) // 'button__text button__text--disabled'
* b(['disabled', 'primary']) // 'button button--disabled button--primary'
* createBEM 接收一个参数 blockName 即组件的名称,返回一个函数,这个函数接收多个参数
* 参数类型可以是,字符串、对象、数组
* 字符串为 element 元素
* 对象或数组为 modifier 修饰符
*/
export function createBEM(name: string) {
return (el?: Mods, mods?: Mods): Mods => {
// 如果第一个参数不是 el(不是字符串),则设置为 modifier (el 只能在第一个参数位置)
if (el && typeof el !== 'string') {
mods = el;
el = '';
}
// 如果 el 为空,则 el 为 blockName
// 否则 el 为: blockName__elName
el = el ? `${name}__${el}` : name;
// 生成带 modifier 的类名
return `${el}${genBem(el, mods)}`;
};
}
classnames模块
帮助你在项目中更好地使用处理 className
用法
import classNames from 'classnames'
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
源码解析
classNames
的实现非常简单,只有一个函数定义,接受任意参数类型和个数
- 简单类型,直接加到集合
- 数组,判断元素真假,加到集合
- 对象,判断value真假,加到集合
var hasOwn = {}.hasOwnProperty;
// 接收任意类型的参数和个数
function classNames() {
// 用于存储解析过的 className,如 ['foo', 'bar']
var classes = [];
// 循环所有参数
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
// 当前参数如果为 false,则跳过,再继续判断下一个
if (!arg) continue;
var argType = typeof arg;
// 如果是 字符串或数值,直接加到集合
if (argType === 'string' || argType === 'number') {
classes.push(arg);
// 如果是 数组,继续递归判断
} else if (Array.isArray(arg)) {
if (arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
}
// 如果是对象
} else if (argType === 'object') {
// 如果是原生object,并且value为真,则把 key 加入到集合
if (arg.toString === Object.prototype.toString) {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
// 有自定义 toString, 直接toString后加到集合
} else {
classes.push(arg.toString());
}
}
}
// 拼成字符中返回
return classes.join(' ');
}
总结
bem
和 classnames
都可以很好帮你管理你的 class
名称,区别在于,bem
更偏向于基于BEM规范的样式类生成,而 classnames
应用更加宽范,没有名称的规则限制,这里我推荐在组件库开发中,使用 bem
,在业务开发中使用 classnames
, 不过 vue
中不需要 classnames
,框架默认支持。
转载自:https://juejin.cn/post/7096310931247857695