实现一个classnames

classnames 是一个能将传入的多个对象或者字符串转换成类名的工具库。在 React开发中, 如果需要条件判断添加类名,classnames可以帮助你很方便的实现。 通过这篇文章,你可以了解到 classnames 的使用方式和实现。

在 React 中 使用

想象下列场景,我们需要封装一个 Button 组件,父组件 props 会传递 loading 和 disabled 属性,如果对应的值为 true,需要给 button 添加对应的类名,我们的代码可能是下面这样:

js 复制代码
function Button({disabled, loading}) {
  let btnClass = 'btn';
	if (disabled) btnClass += ' btn-disabled';
	if (loading) btnClass += ' btn-loading';
  
  return <button className={btnClass}>button</button>
}

借助 classnames,你可以通过一个对象更简单,更直观的表达这种条件类名:

js 复制代码
import classNames from "classnames";
function Button({disabled, loading}) {
    const btnClass = classNames({
    "btn": true,
    "btn-disabled": disabled,
    "btn-loading": loading
  })
  
  return <button className={btnClass}>button</button>
}

如上所示,传入 classnames 函数的参数类型是对象时,会将属性值为真值的属性名输出到类名结果中。classnames 函数可以接收任意数量的参数, string 类型会添加到类名结果中,数组类型的会对每一个元素应用上述规则, 比如:

ts 复制代码
const arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'

实现一个classnames

了解了 classnames 的使用场景和用法之后,就可以照猫画虎实现一个了。classnames的参数类型可以是 string, object或者 array,其他的假值会被忽略。为了简单起见,我们的 Array 类型只包含字符串类型的元素。

classnames 的输出结果就是将类名使用空格隔开的字符串。我们可以将符合规则的类名放入到数组 classArray 中,通过 classArray.join(' ') 输出结果。

1.处理字符串类型的参数

假定参数都是字符串类型的,剩余参数就是结果数组了

js 复制代码
function classnames(...args: string[]): string {
  return args.join(" ")
}

2.处理对象类型的参数

对象类型的,拿出真值的属性名,放到结果数组。

ts 复制代码
function classnames(...args: {[propNames: string]: any}[]): string {
  const classNames: string[] = [];
  for(let i = 0; i < args.length; i++) {
    const value = args[i];
    Object.keys(value).forEach((k) => {
      if(value[k]) classNames.push(k);
    })
  }
  return classNames.join(" ");
}

3.处理数组类型的参数

数组类型的,通过 push, concat...... ,只要将里面的元素加入到结果数组中就可以,这里就选用push了。

ini 复制代码
function classnames(...args: string[][]): string {
  const classNames: string[] = [];
  for (let i = 0; i < args.length; i++) {
    const value = args[i];
    classNames.push(...value);
  }
  return classNames.join(' ');
}

4.简单实现

当前,只要判断出参数类型,依次使用上面的思路输出结果就好了,判断参数类型,有很多种办法,这里就通过调用 Object.prototype.toString 方法,获取对象的 [[Class]] 值,然后解析该值,得到参数类型。

ts 复制代码
const opt = Object.prototype.toString;
function isString(arg: any): arg is string {
  return opt.call(arg) === '[object String]'
}

function isArray(arg: any): arg is any[]{
  return opt.call(arg) === '[object Array]';
}

function isObject(arg: any): arg is {[propName: key]: any}{
  return opt.call(arg) === '[object Object]';
}

最后的代码如下

js 复制代码
type ArgType = string | string[] | Record<string, any> | undefined | null | boolean;
function classnames(...args: argType): string {
  const classNames = [];
  
  for (let i = 0; i < args.length; i++) {
    const value = args[i];
    if (!value) continue;  //跳过假值
    if (isString(value)) {
      classNames.push(value);
    } else if (isArray(value)) {
      classNames.push(...value);
    } else if (isObject(value)) {
      Object.keys(value).forEach((k) => {
        if (value[k]) classNames.push(k);
      });
    }
  }
  return classNames.join(' ');
}

5.边界实现

第四步的结果基本可以满足开发场景了,有一种情况没考虑进去,classnames 库中的数组类型可以包含 argType 中的任意类型, 我们为了实现方便,将其限制成了字符串数组。要实现这个需求,在遇到数组类型的参数时,对每个参数递归的调用 上面编写的 classnames, 就可以 cover这种写法了:

ts 复制代码
function classnames(...args) {
  const classNames = [];
  function appendClass(...args) {
    for (let i = 0; i < args.length; i++) {
      const value = args[i];
      if (!value) continue;
      if (isString(value)) {
        classNames.push(value);
      } else if (isArray(value)) {
        for (const v of value) {
          appendClass(v); //递归调用 appendClass,
        }
      } else if (isObject(value)) {
        Object.keys(value).forEach((k) => {
          if (value[k]) classNames.push(k);
        });
      }
    }
  }
  appendClass(...args); //开始执行
  return classNames.join(' ');
}

这样子,就可以处理下面这种嵌套结构的情况了

js 复制代码
classnames(['111', '222', '333'], 'string1', 'string2', undefined, null, { obj1: true, obj2: false }, [
  { obj3: true, obj4: false },
  'aaa',
  ["res1", "res2"]
]);
// output: 111 222 333 string1 string2 obj1 obj3 aaa res1 res2

总结

本文介绍了在 React 开发中常用的类名处理工具库 classnames 的使用场景和实现方式。在 React 组件中,经常需要根据条件来动态生成类名,而 classnames 可以简化这个过程。通过传递对象、数组或字符串参数,classnames 将它们转换成合适的类名字符串。

文章中实现了一个简化版的 classnames,包括处理字符串、对象和数组类型的参数。对于数组类型的参数,采用了递归调用的方式,以支持嵌套的情况。

第一次尝试写文章,写的不明白或者有问题的地方,希望大佬们可以指出来~

相关推荐
还是大剑师兰特29 分钟前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解30 分钟前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~36 分钟前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding41 分钟前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django
张张打怪兽1 小时前
css-50 Projects in 50 Days(3)
前端·css