本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
classNames, 非常常用,在react中想要动态添加className时,就会使用classNames这个库。如果业务中需要动态添加类名,你或许想到使用三元表达式等方式,但是这样不够直观。如果三元表达式的一部分还是三元表达式,这样就很容易出错。比如下面的例子:
state = {
class1:true,
class2:true
};
// 不使用classNames
render() {
const {class1,class2} = this.state;
let divClass='class-test';
divClass = class1 ? 'class-test-class1' : ' ';
divClass = divClass + class2 ? 'class-test-class2' : ' ';
return (
<div className={divClass}>test</div>
);
}
// 使用classNames
render() {
const {class1,class2} = this.state;
let divClass=classNames({
class-test-class1:class1,
class-test-class2:class2
})
return (
<div className={divClass}>test</div>
);
}
1.学习内容
classNames仓库地址:
https://github.com/JedWatson/classnames.git
2.用例
根据用例了解用法:
var classNames = require('classnames');
classNames('foo', 'bar'); // => 'foo bar'
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'
var arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'
3.猜测源码实现思路
根据用例猜测源码实现思路:
最终给dom设置的class样式都是如下形式的:
<div class="class1 class2 class3"></div>
所以classNames的最终返回结果就是以空格分隔的字符串。classNames是一个函数,接受逗号分隔的参数,而参数可能是字符串,数字,对象,数组;针对对象要筛选出那些值为真值的键,针对数组的每一个元素都要进行检查。所以classNames是一个递归的函数。
4.源码
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
'use strict';
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)) {
// 如果是数组则针对数组中的每一个元素应用classNames函数
if (arg.length) {
var inner = classNames.apply(null, arg);
// 数组中每一个元素获得结果则放入结果集合中
if (inner) {
classes.push(inner);
}
}
} else if (argType === 'object') {
// 如果是对象则检查对象的toString方法和对象类型原型上的toString对比
if (arg.toString === Object.prototype.toString) {
// 如果是对象原型上的toString则依次针对每一个为真值的自有属性,放入结果集合中
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
} else {
// 否则调用此对象的自定义toString方法
classes.push(arg.toString());
}
}
}
// 结果数组使用空格分隔
return classes.join(' ');
}
// 导出逻辑 支持各种导出形式
if (typeof module !== 'undefined' && module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
// register as 'classnames', consistent with npm package name
define('classnames', [], function () {
return classNames;
});
} else {
window.classNames = classNames;
}
}());
整体流程:主要处理逻辑+导出
主要处理逻辑:
1.遍历参数类表arguments,依次检查每一个参数
2.如果参数为假值,略过,检测下一个
3.如果参数为真值,检查参数类型:
(1)数字或者字符串加入到结果集合中
(2)数组则针对数组的每一个元素递归处理获得结果,结果为真值则加入到结果集合中
(3)对象则检查是否重写了toString方法。如果重写了则结果集合中加入调用toString方法后的值;
如果没有重写则依次检查对象的每一个自有属性,为真值则加入到结果集合中
4.结果集合(数组)以空格分隔组成字符串
5.总结收获
总结:classNames为一函数,接收不定数目的参数,参数可以是字符串,可以是对象,可以是数组。针对数字或者字符串将放入结果集中;针对对象只保留值为真的属性;针对数组,遍历每一个数组元素,递归调用classNames函数。
收获:了解classNames的使用方法和实现思路