【源码共读】| classnames

966 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

【若川视野 x 源码共读】第26期 | classnames 点击了解本期详情一起参与

今天阅读的是:Classnames

github.com/JedWatson/c…

image-20221222092958668

  • 这个库是用来拼接class

尝试使用

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'
  • 上述可以知道,这个库可以有条件的拼接className,通过传入一个对象,或字符串,bool值等,输出最终的拼接字符串

源码分析

  • 首先,我们看下入口文件位置在index.js

image-20221222105740033

  • 再看下测试用例,需要实现什么功能
// tests/index.js
    it("keeps object keys with truthy values");

    it("joins arrays of class names and ignore falsy values");

    it("supports heterogenous arguments");

    it("should be trimmed");

    it("returns an empty string for an empty configuration");

    it("supports an array of class names");

    it("joins array arguments with string arguments");

    it("handles multiple array arguments");

    it("handles arrays that include falsy and true values");

    it("handles arrays that include arrays");

    it("handles arrays that include objects");

    it("handles deep array recursion");

    it("handles arrays that are empty");

    it("handles nested arrays that have empty nested arrays");

    it("handles all types of truthy and falsy property values as expected");

    it("handles toString() method defined on object");

    it("handles toString() method defined inherited in object");
  • 保留值为true的值
  • 去除空字符
  • 支持传数组
  • 为空时返回 ' '
  • 支持数组嵌套对象
  • toString()重写

我们来看下源码是怎么实现的

// 自执行函数
(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)) {
                // 如果是数组,通过apply 依次传入,执行 classNames()
                // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
                if (arg.length) {
                    var inner = classNames.apply(null, arg);
                    if (inner) {
                        classes.push(inner);
                    }
                }
            } else if (argType === "object") {
                // 检查参数的toString()是否重写
                // Object.prototype.toString
                // ƒ toString() { [native code] }
                if (
                    arg.toString !== Object.prototype.toString &&
                    !arg.toString.toString().includes("[native code]")
                ) {
                    classes.push(arg.toString());
                    continue;
                }

                for (var key in arg) {
                    // hasOwn的自身属性 && 当前的值为true
                    if (hasOwn.call(arg, key) && arg[key]) {
                        classes.push(key);
                    }
                }
            }
        }

        return classes.join(" ");
    }

    if (typeof module !== "undefined" && module.exports) {
        // export default classNames (common js)
        classNames.default = classNames;
        module.exports = classNames;
    } else if (
        // amd 模式
        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;
    }
})();

总结

这个库的实现流程比较简单,我们可以学到

  • 考虑边界情况的处理
  • 测试用例尽可能完善
  • 使用.apply()处理数组参数
  • 如何判断toString()重写