实现一个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,包括处理字符串、对象和数组类型的参数。对于数组类型的参数,采用了递归调用的方式,以支持嵌套的情况。

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

相关推荐
waylon11113几秒前
【HOC】高阶组件在Vue老项目中的实战应用 - 模块任意排序
前端·vue.js·面试
阳阳羊2 分钟前
Mpx 动画
前端
编程社区管理员2 分钟前
「2025最新版React+Ant Design+Router+TailwindCss全栈攻略:从零到实战,打造高颜值企业级应用
前端·react.js·前端框架
DJA_CR2 分钟前
解决在 TSX 中使用 `RouterView` + `KeepAlive` 不生效问题
前端·vue.js
前端爆冲13 分钟前
项目中无用export的检测方案
前端
热爱编程的小曾41 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin1 小时前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿2 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly