深入classnames源码

classnames解决的问题

在很久之前如果一个组件/元素有很多个状态/属性的时候,通常需要动态的设置多个样式类;比如下面的一个checkbox组件;它拥有size属性,disabled和checked状态。我们经常的做法是如下:

  1. 通过三目运算符(或者if-else),求出正确的classname
  2. 字符串拼接起来
    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>;
    }

这样的做法有二个问题:

  1. 容易出错,需要手动加入空格;
  2. 比较丑陋,不宜阅读,不以维护。

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(' ');
    }

梳理一下整个流程:

  1. 定义一个数组classes(用来存储所有的classname);for遍历一遍classNames接收的所有参数;(支持多个参数)
  2. 判断参数的类型
    1)如果是string或者number,则是单纯的class名称,直接push进classes数组
    2)如果是数组,则classNames.apply(null, arg),将数组分拆成参数(apply第二个参数可以接受数组)传给className函数进行递归,重复1.2步骤;
    3)如果是对象,形如{'is-disabled': disabled}则遍历对象,如果arg[key]为true,则push进classes数组
  3. 最后,将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}]);

好了,由于源码比较简单,就不在废话了。

    原文作者:水中月120
    原文地址: https://segmentfault.com/a/1190000019862404
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞