《classnames源码》阅读笔记

news2025/4/18 20:05:58

源码目录总览

参考官方文档中的内容,我们可以知道classnames 有一个主要版本(index)和两个替代版本 (分别是dedupebind)。在看目录的时候也可以发现 classnames 具有多个对外暴露的入口。

  • index.jsclassnames的主要使用的版本
  • dedupe.jsclassnames中一个可选的,用于删除重复数据的版本
  • bind.jsclassnames中另一个可选的,用于css modules形式的版本
  • *.d.ts是定义类型的文件
  • bower.json是包管理工具bower的配置文件(后面会提到bowernpm的区别)
  • tests/目录下是classnames三个版本的测试文件以及一个适用于测试代码的类型文件
  • benchmarks/目录用于存放benchmarks相关的文件(benchmarks是一个检测代码性能的工具)

用法概述

安装
 # via npmnpm install classnames# via yarn yarn add classnames 
一般用法
 // 引入包import classnames from 'classnames';// 接收多个字符串作为参数classnames('classA', 'classB') // => classA, classB// 接收字符串 和 对象作为参数(对象的值可以为 true || false)classnames('classA', { classB: true }) // => classA, classB// 接收字符串 和 对象作为参数(对象的值也可以是一个返回 true || false 的表达式)classnames('classA', { classB: 1 < 2 }) // => classA// 接收一个对象作为参数,对象的键名是对应的类名,对象的值的规则与上面一致classnames({ classA: true, classB: false }) // => classA// 接收多个对象作为参数,对象的键与值的规则与上面一致classnames({ classA: true }, { classB: true }) // => classA, classB// 接收一个数组作为参数classnames(['classA', { classB: false }, { classC: 1 }]) // => classA, classC// 接收数组和其它类型参数的组合classnames(['classA', { classB: false }], 'classC') // => classA, classC// 需要注意的特殊例子!// 当存在针对同一个类的不同值时,只要其中一个值是真值,就会应用这个类(与这个值的计算顺序无关)classnames('classA', true ? 'classB' : '', { classB: false }) => classA, classBclassnames('classA', { classB: false }, true ? 'classB' : '') => classA, classB 
 // 动态计算类名type UserType = "guest" | "host";const user: UserType = 'guest';classnames(`${user}_type_color`); // => guest_type_color 
dedupe用法

dedupe主要用于解决上面提到的特殊例子中存在的问题

有时候我们会在代码中应用多个样式,同时我们希望是否应用某个样式取决于这个样式最终的计算结果。还是用上面提到的特殊例子:

  • 不使用dedupe````classnames('classA', true ? 'classB' : '', { classB: false }) => classA, classBclassnames('classA', { classB: false }, true ? 'classB' : '') => classA, classBclassnames('classA', { classB: true }, false ? 'classB' : '') => classA, classB ```* 使用dedupe````classnames(‘classA’, true ? ‘classB’ : ‘’, { classB: false }) => classAclassnames(‘classA’, { classB: false }, true ? ‘classB’ : ‘’) => classA, classBclassnames(‘classA’, { classB: true }, false ? ‘classB’ : ‘’) => classA, classB ```##### bind用法

bind适用于通过css modules引入样式并需要动态计算的场景。文档中建议在支持ES6的情况下使用模板字符串的形式来替代bind方法。

直接来看例子

 .foo {color: red;}.bar {font-size: 30px;}.zoo {border: 1px solid red;} 
 import styles from './index.css';const cs = classnames.bind(styles)// foo, zoo<div className={cs('foo', { bar: false }, [{ zoo: true }])}></div> 

看看源码

通用代码

index.js,deeupe.jsbind.js 中都使用到了这个通用代码,用于针对不同的环境去导出classNames方法。

 // 适用于 nodejs 的环境,使用 commonjs 规范引入模块if (typeof module !== 'undefined' && module.exports) {classNames.default = classNames;module.exports = classNames;} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {// 适用于浏览器环境,使用 AMD 规范引入模块// register as 'classnames', consistent with npm package namedefine('classnames', [], function () {return classNames;});} else {// 以上情况均不适用,就把 classNames 函数挂载到 window 上window.classNames = classNames;} 
index.js
(function () {'use strict';// 将空对象的 hasOwnProerty 保存到hasOwn变量// 防止入参对象修改了同名方法导致判断结果错误var hasOwn = {}.hasOwnProperty;function classNames() {// 定义类名数组var classes = [];// 遍历入参for (var i = 0; i < arguments.length; i++) {// 拿到当前第 i 个入参var arg = arguments[i];// 入参的值类型为 falsy 则跳过这个值if (!arg) continue;// 判断当前入参的类型var argType = typeof arg;// 如果当前入参类型为 字符串 || 数字, 就直接推入类名数组if (argType === 'string' || argType === 'number') {classes.push(arg);// 如果当前入参类型为 数组} else if (Array.isArray(arg)) {// 首先判断数组是否为空// 不为空if (arg.length) {// 将入参的数组作为新的参数 递归调用 classNames 函数var inner = classNames.apply(null, arg);// 如果有返回值, 就将本次递归调用的结果推到 类名数组中if (inner) {classes.push(inner);}// 没有结果则忽略// 如果当前入参的类型是 object}} else if (argType === 'object') {// 如果当前入参对象的 toString 不等于 对象原型链上的 toString&& 入参对象的 toString 方法中不包含 "[native code]" 字符if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {// 调用入参对象自定义的 toString 方法后,再将得到的字符串推入 类名数组// TODO: 感觉这里需要额外增加一个判断,// 对入参的 toString 方法的返回值做判断// 如果返回值是一个 (字符串 || 数字) && 当前入参调用 toString 之后的值是真值,就直接推入 类名数组// 否则应当递归调用 classNames 函数(与数组的处理方法一致)if(typeof arg.toString() === 'string' || typeof arg.toString() === 'number' && arg.toString() ) {// && arg.toString()classes.push(arg.toString());continue;} else {classNames.apply(null, arg)}}// 没有修改过入参对象的 toString 方法// 遍历入参对象的 keyfor (var key in arg) {// 如果当前的key 是入参对象本身的属性 并且这个属性的值是真值if (hasOwn.call(arg, key) && arg[key]) {// 将这个属性名推入 类名数组classes.push(key);}}}}// 将 当前类名数组 用空格拼接并返回return classes.join(' ');}// 通用代码...}()); 
dedupe.js

简单来说,dedupe版本为了解决类名重复的问题,构造了一个对象来保存所有的类名;由于对象的key无法重复,所以对象中后定义的类名的值会覆盖之前定义的相同类名的值。在对象构造完成后,再去遍历取得对象中所有值为truekey,并将这些key返回,最终添加到DOM上。

 (function () {'use strict';var classNames = (function () {// don't inherit from Object so we can skip hasOwnProperty check later// http://stackoverflow.com/questions/15518328/creating-js-object-with-object-createnull#answer-21079232// 定义一个“存储对象类”function StorageObject() { }// 将这个构造函数的原型对象清空StorageObject.prototype = Object.create(null);// 用于解析数组入参的方法function _parseArray(resultSet, array) {var length = array.length;for (var i = 0; i < length; ++i) {// 对数组中的每一个参数都进行解析_parse(resultSet, array[i]);}}var hasOwn = {}.hasOwnProperty;// 用于解析数字的方法function _parseNumber(resultSet, num) {// 这里没有对数字的合法性做校验(0, -0, Infinity)// 但是没有关系,在最后一步遍历 list 数组的时候只取真值resultSet[num] = true;}// 用于解析对象的方法function _parseObject(resultSet, object) {// 与 index.js 中类似的判断,检查入参对象是否具有自定义的 toString 方法// 有的话就调用 其自定义的 toString 方法,并将结果添加到 存储对象 中if (object.toString <img src="https://www.smashingmagazine.com/2012/11/writing-fast-memory-efficient-javascript/#de-referencing-misconceptions// 将 当前key 对应的值 转为布尔值之后 存储在 存储对象 中// 这里的逻辑与 index.js 不同,在 index.js 中,是直接将当前的 key 放到最终的 classes 类名数组中// 但是 在 dedupe.js 中,因为去重的需要,所以会先将 key 放在对象中,用于更新它的值resultSet[k] = !!object[k];}}}// 定义一个去重的正则var SPACE = /\s+/;// 用于解析字符串的方法function _parseString(resultSet, str) {// 使用 空格 将字符串分成数组var array = str.split(SPACE);var length = array.length;for (var i = 0; i < length; ++i) {// 遍历数组,将每个字符串都作为 存储对象 上的一个 key, 值默认为 trueresultSet[array[i]] = true;}}function _parse(resultSet, arg) {// 无参 -> 返回if (!arg) return;var argType = typeof arg;// 针对不同类型的入参,进入不同的处理方法// 'foo bar'if (argType === 'string') {_parseString(resultSet, arg);// ['foo', 'bar', ...]} else if (Array.isArray(arg)) {_parseArray(resultSet, arg);// { 'foo': true, ... }} else if (argType === 'object') {_parseObject(resultSet, arg);// '130'} else if (argType === 'number') {_parseNumber(resultSet, arg);}}// 最终返回的供外部调用的 classNames 函数function _classNames() {// don't leak arguments// https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-argumentsvar len = arguments.length;var args = Array(len);for (var i = 0; i < len; i++) {args[i] = arguments[i];}// 创建一个 存储对象var classSet = new StorageObject();// 对入参做解析,并将解析后的值都放到新创建的 存储对象 上_parseArray(classSet, args);// 存储最终类名的数组var list = [];// 遍历 存储对象for (var k in classSet) {// 如果 存储对象 当前 key 的值是真值,就推到 类名数组中if (classSet[k]) {list.push(k)}}// 最后返回一个类名字符串return list.join(' ');}return _classNames;})();// 通用代码...}());" style="margin: auto" />

对比

关于类的处理包除了 classnames 以外,还有一个最近在项目里用到的是 clsx;在我看来,两者的差别就在于 clsx 直接省去了数组转字符串这个步骤,直接定义了一个字符串,然后把符合条件的(真值)的类名加到后面:

总结

这次去看classnames的源码,确实了解了它的工作原理,没有想象的这么复杂;针对通用代码,还去学习了一下从IIFECJS,再从AMDCMDES Module的模块化过程,下次再把这个笔记也输出一下!

冲!

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/104889.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring MVC【参数的获取与数据返回】

Spring MVC【参数的获取与上传】&#x1f34e;一. 获取参数&#x1f352;1.1 获取单个参数&#x1f352;1.2 获取多个参数&#x1f352;1.3 获取多个参数&#x1f352;1.4 获取URL中参数 (PathVariable)&#x1f34e;二. 上传文件 (RequestPart)&#x1f352;2.1 postman模拟上…

目标检测论文解读复现【NO.23】FS-YOLOv5:轻量化红外目标检测方法

前言 此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读最新目标检测算法论文&#xff0…

33个CadQuery程序化建模实例

本文介绍的33个建模示例可以帮助你了解如何使用 CadQuery 构建3D对象。示例是从简单到复杂组织起来的&#xff0c;因此按顺序学习它们是吸收它们的最佳方式。 1、简易矩形板 最简单的例子&#xff0c;一个矩形盒子&#xff1a; result cadquery.Workplane("front&qu…

食品加工企业自营商城小程序开发,帮助企业增加销售渠道,提高销量

随着生活水平的不断提高&#xff0c;人们对于食品质量提出了更高的要求。传统人们购买食品时只能到超市或者市场进行食品采购&#xff0c;需要花费一定的时间和精力&#xff0c;而且经过多层中间商赚取差价的原因性价比也并不高。在移动电商的时代发展下&#xff0c;很多企业商…

『 canvas 动画』为了让老婆彻底理解正弦和余弦,我连夜制作了这个效果

前言 最近在做 canvas 相关的效果时&#xff0c;经常用到三角函数以及正/余弦相关的数字知识&#xff0c;这些知识点都是我们在初中的时候就学过的&#xff0c;但是这么多年基本已经忘的差不多了&#xff0c;刚好最近又学到了一个用 canvas 来实现的正/余弦动画效果&#xff0…

成功转行Python工程师,年薪30W+,经验总结都在这

这是给转行做Python的小白的参考&#xff0c;无论是从零开始&#xff0c;或者是转行的朋友来说&#xff0c;这都是值得一看的&#xff0c;也是可以作为一种借鉴吧。 而且我决定转行IT&#xff08;互联网&#xff09;行业&#xff0c;其实理由也很简单&#xff0c;不用动体力&a…

循环冗余编码(CRC编码)与海明码(考研前突击一下QAQ)

循环冗余编码&#xff08;CRC编码&#xff09;与海明码 一.环冗余编码 1.循环冗余编码的形成 生成多项式&#xff1a;G1011 表示成生成多项式为G(x)X3X1X^3X1X3X1 示例&#xff1a; 假设信息字节为&#xff1a;F1001010 选取生成多项式&#xff08;默认&#xff09;G1011 将…

2022年下半年部分团队的总结

这是 2021 年年底的汇报。 这是 2022 年上半年的汇报。 踏石留印 抓铁有痕 CSDN 是中国 IT 人士学习&#xff0c;成长&#xff0c;成功的平台。除了一些创新的探索之外&#xff0c; 20 多年来&#xff0c;CSDN 团队为这个平台开发和维护着各种基本功能和服务&#xff0c;还进…

自动化测试技术笔记(一):前期调研怎么做

昨天下午在家整理书架&#xff0c;把很多看完的书清理打包好&#xff0c;预约了公益捐赠机构上门回收。 整理的过程中无意翻出了几年前的工作记事本&#xff0c;里面记录了很多我刚开始做自动化和性能测试时的笔记。虽然站在现在的角度来看&#xff0c;那个时候无论是技术细节…

“ 这片绿茵从不缺乏天才,努力才是最终的入场券——梅西 ”

前言 想了又想还是忍不住想发布一篇文章来纪念一下2022年的卡塔尔世界杯&#xff0c;这伟大的诸神黄昏之战。4年一届的世界杯像是一把衡量时间的坐标&#xff0c;正所谓青春不过几届世界杯&#xff01;2014巴西世界杯在上初一&#xff0c;2018俄罗斯世界杯在上高二&#xff0c;…

如何成为一名合格的互联网大厂Python工程师?

Python开发工程师&#xff0c;是一个在IT行业圈子里一直都很热门的话题&#xff0c;无论是像腾讯、百度这样的大型公司&#xff0c;还是刚刚起步的初创公司&#xff0c;都会招python开发工程师。 python已成为越来越多开发者的开发语言选择&#xff0c; 而python开发工程师工资…

[附源码]计算机毕业设计Python架构的博客平台设计(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

【Python计算几何】德劳内三角剖分算法 | scatter 绘制散点图 | Dealunay 函数

猛戳&#xff01;跟哥们一起玩蛇啊 &#x1f449; 《一起玩蛇》&#x1f40d; &#x1f4ad; 写在前面&#xff1a; 本章我们将介绍的是计算机和领域的 Delaunay 三角剖分算法&#xff08;即德劳内三角剖分&#xff09;&#xff0c;它是一种用于将点集划分成三角形网格的算法。…

如何同时启动Android平台GB28181设备接入模块和轻量级RTSP服务模块?

技术背景 在介绍GB28181设备接入模块和轻量级RTSP服务之前&#xff0c;我们需要先搞清楚&#xff0c;二者的使用场景和技术设计的差别&#xff1a; 首先是GB28181设备接入模块&#xff1a; 为什么要设计GB28181设备接入模块&#xff1f;GB28181接入SDK&#xff0c;实现不具备…

软件:分享六款实用的软件,每一款值得收藏

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

[XCTF]funny_video(难度2)

目录 一、题目重述 二、解题思路 1.分解音视频 2.处理音频 总结 前言 视频夹杂着一段音频&#xff0c;怎么提取&#xff1f;遇到一款新的工具&#xff01;MKVToolNix 特此记录&#xff01; 一、题目重述 一段视频&#xff0c;观看之后发现有一段还夹杂着音频。 XCTF-fu…

避坑指南!Python里面的这九个坑,坑的就是你

Python里面有一些坑&#xff0c;让你防不胜防&#xff0c;菜鸟经常会弄晕&#xff0c;而学习多年的Python老鸟也会时不时中招。小编整理了9个坑&#xff0c;都是会经常碰到的坑&#xff0c;让你大呼我曾经也碰到过! 虽然是小的问题&#xff0c;但是在实际的项目中&#xff0c;哪…

测出让人血压升高的页面崩溃,我是如何排查的

前情回顾 前几天在一次web应用测试过程中&#xff0c;前端发起了向后端接口的查询请求&#xff0c;由于后端响应较慢&#xff0c;前端一直处于等待响应返回状态。在几分钟后&#xff0c;突然页面出现让人惊悚的“噢噢&#xff0c;页面崩溃了”几个大字。 看到这几个字的一瞬间…

用于销售、报告等的 LearnDash Group Management LMS分组管理插件

目录 获取强大、直观的LearnDash LMS组管理和报告 使用 LearnDash Groups LMS分组管理插件进行 B2B 销售 节省设置分组的时间或让客户自己构建和购买&#xff01; 获取强大、直观的LearnDash LMS组管理和报告 LearnDash分组是将学生组织成逻辑单元以进行报告和课程访问的绝…

java回顾:Maven高级

目录 一、私服搭建 二、Maven高级 2.1、依赖范围 2.2、依赖传递 2.3、依赖可选 2.4、依赖排除 2.5、依赖冲突 三、ssm工程改造成分层构建 3.1、maven的继承 3.2、继承的一些应用 3.3、maven的聚合&#xff08;多模块开发&#xff09; 一、私服搭建 https://blog.…