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

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

相关推荐
守城小轩1 小时前
JavaScript vs Python 用于 Web Scraping(2025):终极对比指南
前端·chrome·chrome devtools·指纹浏览器·浏览器开发·超级浏览器
风逸hhh4 小时前
python打卡day29@浙大疏锦行
开发语言·前端·python
LuckyLay4 小时前
Vue百日学习计划Day33-35天详细计划-Gemini版
前端·vue.js·学习
ᖰ・◡・ᖳ4 小时前
JavaScript:PC端特效--缓动动画
开发语言·前端·javascript·css·学习·html5
会飞的鱼先生5 小时前
vue2、vue3项目打包生成txt文件-自动记录打包日期:git版本、当前分支、提交人姓名、提交日期、提交描述等信息 和 前端项目的版本号json文件
前端·vue.js·git·json
!win !6 小时前
uni-app项目从0-1基础架构搭建全流程
前端·uni-app
c_zyer6 小时前
使用 nvm 管理 Node.js 和 npm 版本
前端·npm·node.js
布Coder6 小时前
前端 vue + element-ui 框架从 0 - 1 搭建
前端·javascript·vue.js
i_am_a_div_日积月累_6 小时前
Element Plus 取消el-form-item点击触发组件,改为原生表单控件
前端·vue.js·elementui
集成显卡6 小时前
网页 H5 微应用接入钉钉自动登录
前端·后端·钉钉