classnames解决的问题
在很久之前如果一个组件/元素有很多个状态/属性的时候,通常需要动态的设置多个样式类;比如下面的一个checkbox组件;它拥有size属性,disabled和checked状态。我们经常的做法是如下:
- 通过三目运算符(或者if-else),求出正确的classname
- 字符串拼接起来
render () {
let { title = "", disabled = false, children, style, size } = this.props;
let { checked } = this.state;
return <label style={style} className={"el-checkbox-button" +
(size ? ' el-checkbox-button--' + size : '') +
(disabled ? ' is-disabled' : "") +
(checked ? ' is-checked' : "")
}>
...
</label>;
}
这样的做法有二个问题:
- 容易出错,需要手动加入空格;
- 比较丑陋,不宜阅读,不以维护。
classnames的出现帮我们解决了上面的问题;下面我用classnames改写一下上面的代码。
代码如下:
render () {
let { title = "", disabled = false, children, style, size } = this.props;
let { checked } = this.state;
return <label style={style}
className={classnames('el-checkbox-button--' + size, {
'is-disabled': disabled,
'is-checked': checked
)}>
....
</label>;
}
源码分析
github地址:https://github.com/JedWatson/…;
核心源码大概30行,下面我们结合源码看一下它是怎么实现的。
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
梳理一下整个流程:
- 定义一个数组classes(用来存储所有的classname);for遍历一遍classNames接收的所有参数;(支持多个参数)
- 判断参数的类型
1)如果是string或者number,则是单纯的class名称,直接push进classes数组
2)如果是数组,则classNames.apply(null, arg),将数组分拆成参数(apply第二个参数可以接受数组)传给className函数进行递归,重复1.2步骤;
3)如果是对象,形如{'is-disabled': disabled}
则遍历对象,如果arg[key]为true,则push进classes数组 - 最后,将classes用空格 拼接成字符串,并返回。
代码十分简洁优雅。
使用示例
从源码来看,classNames可以接入的参数是多种多样的,下面列举一些。
//字符串(可以单个,多个字符串)
classNames('el-checkbox-button--m', "is-disabled");
//对象(可以单个,多个)
classNames({"is-disabled":true}, {'is-disabled': disabled});
//字符串+对象
classNames('el-checkbox-button--m', {"is-disabled":true, 'is-disabled': disabled});
//数组(数组项可以是字符串,对象)
classNames(['el-checkbox-button--m', "is-disabled"]);
classNames(['el-checkbox-button--m', {"is-disabled":true,'is-disabled': disabled}]);
好了,由于源码比较简单,就不在废话了。