# clsx 和 classnames

这两个 npm 包的功能是一样的，都是为了让构造 `className` 的时候方便带上条件，至于为什么会出现两个包，而且周安装量都有 1000 多万，我是不理解的，所以就抱着好奇的心态，看看他们具体是怎么做的，有什么差异。

# classnames

完整代码如下：

```jsx
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

```jsx
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` 来兼容老浏览器，并且写的语句比较少，几乎是合并定义的。

# 对比

从参数上看，两者的差异在于“**是否会忽略原型链上的属性**”：

```jsx
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。](https://www.measurethat.net/Benchmarks/Show/7642/2/classnames-vs-clsx-vs-alternatives%E3%80%82)

这个网站做了一系列对比，包括两者和一些替代写法，最终能看出 clsx 比 classnames 的性能要高一些。

# 总结

所以并没有什么非 clsx 不可的点啊，主要就一些性能上的差异，功能上的差异微乎其微。。。
