做中东服装有什么网站,企业电子商务网站建设规划,wordpress短代码可视化,大气的公司名字源码阅读#xff1a;classnames 源码阅读#xff1a;classnames简介源码解读indexdedupebind类型声明 学习与收获 源码阅读#xff1a;classnames
简介
classnames 一个简单的 JavaScript 实用程序#xff0c;用于有条件地将类名连接在一起。
可以通过 npm 包管理器从 n… 源码阅读classnames 源码阅读classnames简介源码解读indexdedupebind类型声明 学习与收获 源码阅读classnames
简介
classnames 一个简单的 JavaScript 实用程序用于有条件地将类名连接在一起。
可以通过 npm 包管理器从 npm 注册表上下载
npm install classnamesclassNames 函数接受任意数量的参数可以是字符串或对象。参数 foo 是 { foo: true } 的缩写。如果与给定键关联的值是假的则该键将不会包含在输出中。
classNames(foo, bar); // foo bar
classNames(foo, { bar: true }); // foo bar
classNames({ foo-bar: true }); // foo-bar
classNames({ foo-bar: false }); //
classNames({ foo: true }, { bar: true }); // foo bar
classNames({ foo: true, bar: true }); // foo bar// 支持不同类型的参数同时传入
classNames(foo, { bar: true, duck: false }, baz, { quux: true }); // foo bar baz quuxclassNames(null, false, bar, undefined, 0, 1, { baz: null }, ); // bar 1// 数组将按照上述规则递归展平
const arr [b, { c: true, d: false }];
classNames(a, arr); // a b c
// 相当于
classNames(a, b, { c: true, d: false }); // a b clet buttonType primary;
classNames({ [btn-${buttonType}]: true });在 React 中使用 下面这段代码实现了一个具有交互功能的按钮组件。按钮的样式类名将根据按钮的状态动态改变从而实现按钮按下和鼠标悬停的反馈效果。
import React, { useState } from react;export default function Button (props) {const [isPressed, setIsPressed] useState(false);const [isHovered, setIsHovered] useState(false);let btnClass btn;if (isPressed) btnClass btn-pressed;else if (isHovered) btnClass btn-over;return (buttonclassName{btnClass}onMouseDown{() setIsPressed(true)}onMouseUp{() setIsPressed(false)}onMouseEnter{() setIsHovered(true)}onMouseLeave{() setIsHovered(false)}{props.label}/button);
}而使用classnames库来动态生成按钮的类名
import React, { useState } from react;
import classNames from classnames;export default function Button (props) {const [isPressed, setIsPressed] useState(false);const [isHovered, setIsHovered] useState(false);const btnClass classNames({btn: true,btn-pressed: isPressed,btn-over: !isPressed isHovered,});return (buttonclassName{btnClass}onMouseDown{() setIsPressed(true)}onMouseUp{() setIsPressed(false)}onMouseEnter{() setIsHovered(true)}onMouseLeave{() setIsHovered(false)}{props.label}/button);
}btn: true键为btn表示按钮应该包含类名 btn。btn-pressed: isPressed键为btn-pressed表示当isPressed为true时按钮应该包含类名btn-pressed。btn-over: !isPressed isHovered键为btn-over表示当isPressed为false且isHovered为true时按钮应该包含类名btn-over。
因为可以将对象、数组和字符串参数混合在一起所以支持可选的 className prop属性也更简单因为结果中只包含真实参数
const btnClass classNames(btn, this.props.className, {btn-pressed: isPressed,btn-over: !isPressed isHovered,
});此外作者还提供了另外两个版本dedupe 版本和 bind 版本。
其中dedupe 版本可以正确地删除类的重复数据并确保从结果集中排除后面参数中指定的虚假类。但是此版本速度较慢大约 5 倍因此它作为一个可选的版本。
const classNames require(classnames/dedupe);classNames(foo, foo, bar); // foo bar
classNames(foo, { foo: false, bar: true }); // bar而另一个bind 版本可以让你结合 css-modules以便在组件中动态地添加或删除 CSS 类名同时保证 css-modules 的作用域。
css-modules 是一种在项目中使用局部作用域的 CSS 的方法。它通过给每个类名添加一个唯一的哈希值确保类名在整个应用程序中是唯一的避免了全局作用域的类名冲突。
const classNames require(classnames/bind);const styles {foo: abc,bar: def,baz: xyz,
};const cx classNames.bind(styles);const className cx(foo, [bar], { baz: true }); // abc def xyz下面是一个使用classnames的bind版本结合css-modules的示例
import { useState } from react;
import classNames from classnames/bind;
import styles from ./submit-button.css;const cx classNames.bind(styles);export default function SubmitButton ({ store, form }) {const [submissionInProgress, setSubmissionInProgress] useState(store.submissionInProgress);const [errorOccurred, setErrorOccurred] useState(store.errorOccurred);const [valid, setValid] useState(form.valid);const text submissionInProgress ? Processing... : Submit;const className cx({base: true,inProgress: submissionInProgress,error: errorOccurred,disabled: valid,});return button className{className}{text}/button;
}源码解读
由于代码比较短这里便直接放源码并在其中加上了注释读者可自行阅读
index
/*!Copyright (c) 2018 Jed Watson.Licensed under the MIT License (MIT), seehttp://jedwatson.github.io/classnames
*/
/* global define */(function () {use strict;var hasOwn {}.hasOwnProperty;function classNames() {// 用于存储生成的类名数组var classes [];for (var i 0; i arguments.length; i) {// 获取当前参数var arg arguments[i];// 如果参数为空或为false则跳过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);}}} else if (argType object) {// 判断 object 是否是一个自定义对象// 因为原生的 JavaScript 对象例如 Array、Object 等的 toString 方法包含 [native code]if (arg.toString ! Object.prototype.toString !arg.toString.toString().includes([native code])) {classes.push(arg.toString());continue;}for (var key in arg) {if (hasOwn.call(arg, key) arg[key]) {// 如果参数是对象并且对象的属性值为真则将属性名添加到类名数组中classes.push(key);}}}}// 将类名数组通过空格连接成字符串并返回return classes.join( );}// 判断是否在CommonJS环境下如果是则将classNames赋值给module.exportsif (typeof module ! undefined module.exports) {classNames.default classNames;module.exports classNames;} else if (typeof define function typeof define.amd object define.amd) {// 如果在AMD环境下则将classnames函数注册为模块并将其命名为classnamesdefine(classnames, [], function () {return classNames;});} else {// 在浏览器环境下将classnames函数挂载到全局的window对象上window.classNames classNames;}
}());
dedupe
/*!Copyright (c) 2018 Jed Watson.Licensed under the MIT License (MIT), seehttp://jedwatson.github.io/classnames
*/
/* global define */(function () {use strict;var classNames (function () {// 创建一个不继承自Object的空对象以便后面可以跳过hasOwnProperty的检查function StorageObject() {}StorageObject.prototype Object.create(null);// 解析数组将数组中的每个元素解析为classNamesfunction _parseArray (resultSet, array) {var length array.length;for (var i 0; i length; i) {_parse(resultSet, array[i]);}}var hasOwn {}.hasOwnProperty;// 解析数字将数字作为classNames的属性function _parseNumber (resultSet, num) {resultSet[num] true;}// 解析对象将对象的属性作为classNames的属性function _parseObject (resultSet, object) {// 判断 object 是否是一个自定义对象// 因为原生的 JavaScript 对象例如 Array、Object 等的 toString 方法包含 [native code]if (object.toString ! Object.prototype.toString !object.toString.toString().includes([native code])) {resultSet[object.toString()] true;return;}for (var k in object) {if (hasOwn.call(object, k)) {// set value to false instead of deleting it to avoid changing object structure// https://www.smashingmagazine.com/2012/11/writing-fast-memory-efficient-javascript/#de-referencing-misconceptionsresultSet[k] !!object[k];}}}var SPACE /\s/;// 解析字符串将字符串按照空格分割为数组并将数组中的每个元素作为classNames的属性function _parseString (resultSet, str) {var array str.split(SPACE);var length array.length;for (var i 0; i length; i) {resultSet[array[i]] true;}}// 解析参数根据参数的类型调用相应的解析函数function _parse (resultSet, arg) {if (!arg) return;var argType typeof arg;// 处理字符串类型的参数// foo barif (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);}}// 主函数function _classNames () {// 避免arguments泄漏var len arguments.length;var args Array(len);for (var i 0; i len; i) {args[i] arguments[i];}// 创建一个存储classNames的对象var classSet new StorageObject();// 解析参数并将结果存储在classSet对象中_parseArray(classSet, args);var list [];// 将classSet中值为true的属性加入到list数组中for (var k in classSet) {if (classSet[k]) {list.push(k)}}return list.join( );}return _classNames;})();// 判断是否在CommonJS环境下如果是则将classNames赋值给module.exportsif (typeof module ! undefined module.exports) {classNames.default classNames;module.exports classNames;} else if (typeof define function typeof define.amd object define.amd) {// 如果在AMD环境下则将classnames函数注册为模块并将其命名为classnamesdefine(classnames, [], function () {return classNames;});} else {// 在浏览器环境下将classnames函数挂载到全局的window对象上window.classNames classNames;}
}());
bind
/*!Copyright (c) 2018 Jed Watson.Licensed under the MIT License (MIT), seehttp://jedwatson.github.io/classnames
*/
/* global define */(function () {use strict;var hasOwn {}.hasOwnProperty;function classNames () {// 用于存储生成的类名数组var classes [];for (var i 0; i arguments.length; i) {// 获取当前参数var arg arguments[i];// 如果参数为空或为false则跳过if (!arg) continue;var argType typeof arg;// 如果参数是字符串或数字则直接添加到类名数组中if (argType string || argType number) {classes.push(this this[arg] || arg);} else if (Array.isArray(arg)) {// 如果参数是数组则递归调用classnames函数并将数组作为参数传入classes.push(classNames.apply(this, arg));} else if (argType object) {// 判断 object 是否是一个自定义对象// 因为原生的 JavaScript 对象例如 Array、Object 等的 toString 方法包含 [native code]if (arg.toString ! Object.prototype.toString !arg.toString.toString().includes([native code])) {classes.push(arg.toString());continue;}for (var key in arg) {if (hasOwn.call(arg, key) arg[key]) {// 如果参数是对象并且对象的属性值为真则将属性名添加到类名数组中classes.push(this this[key] || key);}}}}return classes.join( );}// 判断是否在CommonJS环境下如果是则将classNames赋值给module.exportsif (typeof module ! undefined module.exports) {classNames.default classNames;module.exports classNames;} else if (typeof define function typeof define.amd object define.amd) {// 如果在AMD环境下则将classnames函数注册为模块并将其命名为classnamesdefine(classnames, [], function () {return classNames;});} else {// 在浏览器环境下将classnames函数挂载到全局的window对象上window.classNames classNames;}
}());
与 index.js 相比这个版本增加了对this上下文的处理。
类型声明
// 以下类型声明主要用于定义一个名为 classNames 的命名空间和相关的类型。
// 在这个声明中classNames 命名空间中定义了一些类型和接口
declare namespace classNames {// Value 是一个联合类型表示可以接受的值的类型包括字符串、数字、布尔值、未定义和空值type Value string | number | boolean | undefined | null;// Mapping 是一个类型别名表示一个键值对的集合其中键是字符串值可以是任何类型type Mapping Recordstring, unknown;// ArgumentArray 是一个接口继承自数组类型 ArrayArgument表示一个参数数组其中每个元素都是 Argument 类型interface ArgumentArray extends ArrayArgument {}// ReadonlyArgumentArray 是一个接口继承自只读数组类型 ReadonlyArrayArgument表示一个只读的参数数组interface ReadonlyArgumentArray extends ReadonlyArrayArgument {}// Argument 是一个联合类型表示可以作为参数的类型可以是 Value、Mapping、ArgumentArray 或 ReadonlyArgumentArraytype Argument Value | Mapping | ArgumentArray | ReadonlyArgumentArray;
}// 定义了一个名为 ClassNames 的接口它是一个函数类型可以接受 classNames.ArgumentArray 类型的参数并返回一个字符串
interface ClassNames {(...args: classNames.ArgumentArray): string;default: ClassNames;
}declare const classNames: ClassNames;// 通过 export as namespace 来将 classNames 声明为全局命名空间
export as namespace classNames;
// 使用 export 来导出 classNames使其可以在其他模块中使用
export classNames;
学习与收获
使用严格模式
在源码开头使用严格模式的主要原因是为了确保代码的质量和可靠性。严格模式可以帮助开发者避免一些常见的错误和不规范的语法同时也提供了更严格的错误检查和更清晰的错误提示。使用严格模式可以减少一些隐患提高代码的可维护性和可读性。
此外严格模式还可以禁止一些潜在的危险行为例如禁止使用未声明的变量、禁止对只读属性赋值、禁止删除变量等。这可以提高代码的安全性减少一些潜在的漏洞和安全风险。
因此为了确保代码的质量、可靠性和安全性许多开发者选择在源码开头使用严格模式。这样可以强制要求代码符合更严格的规范减少错误和潜在的问题并提高代码的可维护性和可读性。
创建一个不继承自 Object 的空对象
Object.create(null) 是一个创建一个新对象的方法该对象没有原型链也就是没有继承任何属性和方法。这意味着该对象没有内置的属性和方法只能通过直接赋值来添加属性和方法。使用 Object.create(null) 创建的对象被称为“纯净对象”或“字典对象”它适用于需要一个纯粹的键值对集合而不需要继承的场景。在这种对象中键和值可以是任何类型的数据而不仅限于字符串。
在代码中使用了 StorageObject.prototype Object.create(null); 创建了一个不继承自 Object 的空对象 StorageObject。这样可以跳过 hasOwnProperty 的检查提高代码的性能。 解析不同类型的参数包括字符串、数组、对象和数字 设计模式 单例模式是一种创建型设计模式用于确保某个类只有一个实例并提供一个全局访问点来访问该实例。 单例模式通过立即执行函数包裹代码在执行函数内部创建了一个classNames对象并将其赋值给全局变量window.classNames。这样就保证了只有一个classNames对象存在其他地方无法再创建新的classNames对象。 工厂模式是一种创建型设计模式它提供了一种创建对象的接口但具体创建的对象类型可以在运行时确定。工厂模式可以分为简单工厂模式、工厂方法模式和抽象工厂模式。 简单工厂模式也称为静态工厂模式它直接使用一个静态方法来创建对象。工厂方法模式也称为虚拟工厂模式它定义了一个工厂接口并由不同的具体工厂实现来创建不同的对象。 工厂模式通过工厂函数_classNames()创建classNames对象该对象可以根据不同的参数类型调用不同的解析函数来解析参数并将结果存储在classSet对象中。
判断运行环境并导出 classNames
根据不同的运行环境判断是否在 CommonJS 环境下、AMD 环境下或浏览器环境下。如果在 CommonJS 环境下将 classNames 赋值给 module.exports如果在 AMD 环境下将 classNames 注册为模块并命名为 classnames如果在浏览器环境下将 classNames 挂载到全局的 window 对象上。
TypeScript 类型声明
命名空间声明使用declare namespace可以定义一个命名空间将相关的类型和接口组织在一起防止命名冲突并提供模块化的结构。类型别名和联合类型使用 type 关键字可以定义类型别名方便重复使用复杂的类型。联合类型可以用于表示一个值可以是多个不同类型之一。接口和继承使用 interface 关键字可以定义接口表示一种对象的结构。接口可以继承自其他接口通过继承可以复用已有的接口定义。函数类型可以使用接口来定义函数类型指定函数的参数类型和返回值类型。类型导出和模块导入使用 export 关键字可以将类型或值导出使其可以在其他模块中使用。使用 import 关键字可以在其他模块中导入已导出的类型或值。