easycodesniper

blog by chen qiyi

classnames源码阅读

前言

classnames 是一个JavaScript工具库,用于 有条件地 将不同的class类名组合在一起

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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'

// 多种不同类型的参数
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'

// 假值对应的类名都会被忽略
classNames(null, false, 'bar', undefined, 0, { baz: null }, ''); // => 'bar'

// 支持动态类名
let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });

// 在React中的使用:将classNames赋值给元素的className属性即可
export default function App() {

return (
<div className={classNames(...)}></div>
)
}

源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 获取对象的hasOwnProperty方法,该方法用于判断某个属性是否是对象本身的,而不是继承自原型链
const hasOwn = {}.hasOwnProperty;

export default function classNames () {
let classes = '';

for (let i = 0; i < arguments.length; i++) {
const arg = arguments[i];
if (arg) {
classes = appendClass(classes, parseValue(arg));
}
}

return classes;
}

function parseValue (arg) {
// 如果是字符串,直接返回
if (typeof arg === 'string') {
return arg;
}
// 如果既不是字符串,也不是对象和数组,就返回空字符串
if (typeof arg !== 'object') {
return '';
}
// 如果是数组,调用classNames来返回一个组合好的结果字符串
if (Array.isArray(arg)) {
return classNames.apply(null, arg);
}
// arg.toString !== Object.prototype.toString 表示arg的toString方法不是继承自Object上
/**
* !arg.toString.toString().includes('[native code]')
* 如果arg有toString方法,进一步检查这个方法是否是原生的
* 方法是将 arg.toString 方法转换成字符串,然后检查它是否包含字符串 '[native code]'。如果一个函数的字符串表示中包含 '[native code]',这通常意味着函数是原生提供的,而不是用户定义的。
*/
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
return arg.toString();
}

let classes = '';

// 如果arg是对象
for (const key in arg) {
// hasOwn 判断key是否是arg对象本身的属性,而不是继承自原型链的属性
// arg[key] 判断key对应的值是否存在,且不为false
if (hasOwn.call(arg, key) && arg[key]) {
classes = appendClass(classes, key);
}
}

return classes;
}

function appendClass (value, newClass) {
if (!newClass) {
return value;
}

return value ? (value + ' ' + newClass) : newClass;
}