clsx 和 classnames

这两个 npm 包的功能是一样的,都是为了让构造 className 的时候方便带上条件,至于为什么会出现两个包,而且周安装量都有 1000 多万,我是不理解的,所以就抱着好奇的心态,看看他们具体是怎么做的,有什么差异。
classnames
完整代码如下:
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 '';
}
if (Array.isArray(arg)) {
return classNames.apply(null, arg);
}
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
return arg.toString();
}
let classes = '';
for (const key in arg) {
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;
}
appendClass
这个函数的功能就是拼接字符串,没有过多的类型判断,因为传参的时候已经做好了检查。
value 是已有字符串,newClass 是新的字符串,两者都有存在时就带上空格拼接。
classNames
classNames 接受多个参数,用 arguments 属于兼容老旧浏览器的写法,因为那些浏览器不支持 ES5,没有 spread 语法。
接着遍历每一个参数,如果转换为 boolean 之后是 true,那么把参数传给 parseValue 进行转换成字符串,然后合并起来。
parseValue
这个函数就是对 arg 进行解析了,到这里 arg 肯定是 truly 的。
他会对类型做检查,抛弃了 number 和 boolean,对于数组类型会当做参数去调用 classNames(得到字符串),对于对象,他会找到非原型链上的属性,检查真假后将 key 拼接到字符串。
clsx
function toVal(mix) {
var k, y, str='';
if (typeof mix === 'string' || typeof mix === 'number') {
str += mix;
} else if (typeof mix === 'object') {
if (Array.isArray(mix)) {
var len=mix.length;
for (k=0; k < len; k++) {
if (mix[k]) {
if (y = toVal(mix[k])) {
str && (str += ' ');
str += y;
}
}
}
} else {
for (y in mix) {
if (mix[y]) {
str && (str += ' ');
str += y;
}
}
}
}
return str;
}
export function clsx() {
var i=0, tmp, x, str='', len=arguments.length;
for (; i < len; i++) {
if (tmp = arguments[i]) {
if (x = toVal(tmp)) {
str && (str += ' ');
str += x
}
}
}
return str;
}
export default clsx;
toVal
功能上整合了 appendClass 和 parseValue,但没有排除 number,number 会自动转成字符串拼接。
判断出是数组后遍历,然后将每个值递归调用 toVal 去拼接。
对于对象类型,他使用 in 关键字去遍历对象,拿到对象的 key,这里没有判断 key 是否属于对象自身,所以会把原型链上的 key 都带上去。
clsx
一样是用了 arguments 来兼容老浏览器,并且写的语句比较少,几乎是合并定义的。
对比
从参数上看,两者的差异在于“是否会忽略原型链上的属性”:
a = {foo: true}
b = {}
b.__proto__ = a
clsx(b) // 'foo'
classNames(c) // ''
对于重复的值,两者都不会过滤掉,不过 classnames 有提供一个特殊的版本,可以过滤掉重复项。
性能
性能上的差异可以参考 https://www.measurethat.net/Benchmarks/Show/7642/2/classnames-vs-clsx-vs-alternatives。
这个网站做了一系列对比,包括两者和一些替代写法,最终能看出 clsx 比 classnames 的性能要高一些。
总结
所以并没有什么非 clsx 不可的点啊,主要就一些性能上的差异,功能上的差异微乎其微。。。




