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

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

相关推荐
乐多_L44 分钟前
使用vue3框架vue-next-admin导出表格excel(带图片)
前端·javascript·vue.js
南望无一1 小时前
React Native 0.70.x如何从本地安卓源码(ReactAndroid)构建
前端·react native
Mike_188702783511 小时前
1688代采下单API接口使用指南:实现商品采集与自动化下单
前端·python·自动化
鲨鱼辣椒️面1 小时前
HTML视口动画
前端·html
一小路一1 小时前
Go Web 开发基础:从入门到实战
服务器·前端·后端·面试·golang
堇舟1 小时前
HTML第一节
前端·html
纯粹要努力1 小时前
前端跨域问题及解决方案
前端·javascript·面试
小刘不知道叫啥1 小时前
React源码揭秘 | 启动入口
前端·react.js·前端框架
kidding7232 小时前
uniapp引入uview组件库(可以引用多个组件)
前端·前端框架·uni-app·uview
合法的咸鱼2 小时前
uniapp 使用unplugin-auto-import 后, vue文件报红问题
前端·vue.js·uni-app