【若川视野 x 源码共读】第26期 | classnames

130 阅读2分钟

1. 学习任务和目标

2. 学习过程

classnames 是一个简单的工具库,用于有条件地将类名连接在一起。

2.1 如何使用

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'

// 动态类名
let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });


// 在 react 组件中,可以将其设置为 state,通过绑定事件的回调函数对其进行更改,使其类名动态更改、样式动态更改
class Button extends React.Component {
  // ...
  render () {
    var btnClass = 'btn';
    if (this.state.isPressed) btnClass += ' btn-pressed';
    else if (this.state.isHovered) btnClass += ' btn-over';
    return <button className={btnClass}>{this.props.label}</button>;
  }
}

// 结合 react 的对象形式
var classNames = require('classnames');

class Button extends React.Component {
  // ...
  render () {
    var btnClass = classNames({
      btn: true,
      'btn-pressed': this.state.isPressed,
      'btn-over': !this.state.isPressed && this.state.isHovered
    });
    return <button className={btnClass}>{this.props.label}</button>;
  }
}

// 结合 props
var btnClass = classNames('btn', this.props.className, {
  'btn-pressed': this.state.isPressed,
  'btn-over': !this.state.isPressed && this.state.isHovered
});

2.2 classnames 源码

/*!
  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++) { // 传入参数不限制数量,用到参数对象 arguments 
      var arg = arguments[i]; // 遍历 arguments 拿到每一项
      if (!arg) continue; // 如果该项的值为 undefined、null之类的就直接跳过

      var argType = typeof arg; // 获取该项的类型

     // 字符串或者数字之类的直接加入 classes 中
      if (argType === 'string' || argType === 'number') {
        classes.push(arg);
      } else if (Array.isArray(arg)) {
        if (arg.length) {
        // 针对数组中的每一项都需要进行判断是否能够加入 classes 中,所以利用 递归+apply 达到数组扁平化的效果
          var inner = classNames.apply(null, arg); 
          if (inner) { // 递归调用返回的不是空字符串的话就加入 classes
            classes.push(inner);
          }
        }
      } else if (argType === 'object') { // 对象的情况下
      // 如果自带的 toString 方法 和 Object 的不一样
        if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) { 
          classes.push(arg.toString()); // 用自身自定义的 toString 方法
          continue;
        }

        for (var key in arg) {
          if (hasOwn.call(arg, key) && arg[key]) { // 如果该属性是自身的 && value 为 true(或者说 可以转变为 true)
            classes.push(key);
          }
        }
      }
    }

    return classes.join(' ');
  }

  // 支持各种导出方式
  if (typeof module !== 'undefined' && module.exports) { // CommonJS
    classNames.default = classNames;
    module.exports = classNames;
    // AMD, 通过判断是否又 define 方法以及 define.amd 是否为 object
  } 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;
  }
}());

3. 简单总结

3.1 二维数组扁平化的方法

// 使用 apply 方法,apply方法接收的参数是一个包含了若干个参数的数组(也正是因为这样,可以用于数组扁平化)
let a1=[[12,21],[1,2,3],[2,3,4]];
function turn(arr){
   return [].concat.apply([],arr); 
}
console.log('res: ', turn(a1)); // [12, 21, 1, 2, 3, 2, 3, 4]
// 方法2:使用递归
let res_arr = [];
let fun = (arr) => {
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
            fun(arr[i])
        } else {
            res_arr.push(arr[i])
        }
    }
}
fun([[12,21],[1,2,3],[2,3,4]]);
console.log(res_arr) // [12, 21, 1, 2, 3, 2, 3, 4]

其他方法:flat() 该方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

var newArray = arr.flat([depth]);
// depth 可选,指定要提取嵌套数组的深度,默认值为 1。

3.2 求数组中的最大最小值

let arr = [2,3,1,4,5,6]
Math.max.apply(null, arr);  // 获取数组中最大值  6
Math.min.apply(null, arr);  // 获取数组中最小值  1

其他

  1. 笔记参考:【若川视野 x 源码共读】经常用的 classNames,你知道是怎么实现的么