深刻classnames源碼

classnames解決的問題

在好久以前若是一個組件/元素有不少個狀態/屬性的時候,一般須要動態的設置多個樣式類;好比下面的一個checkbox組件;它擁有size屬性,disabled和checked狀態。咱們常常的作法是以下:git

  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>;
    }

這樣的作法有二個問題:github

  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行,下面咱們結合源碼看一下它是怎麼實現的。app

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能夠接入的參數是多種多樣的,下面列舉一些。this

//字符串(能夠單個,多個字符串)
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}]);

好了,因爲源碼比較簡單,就不在廢話了。code

相關文章
相關標籤/搜索