Lecture du code source : noms de classes

Lecture du code source : noms de classes

Introduction

classnamesUn utilitaire JavaScript simple pour concaténer conditionnellement les noms de classe.

Il peut être téléchargé depuis le registre via npmle gestionnaire de packages npm:

npm install classnames

classNamesLes fonctions acceptent n'importe quel nombre d'arguments, qui peuvent être des chaînes ou des objets. Le paramètre 'foo'est l'abréviation de { foo: true }. Si la valeur associée à une clé donnée est fausse, la clé ne sera pas incluse dans la sortie.

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 quux'

classNames(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 c'

let buttonType = 'primary';
classNames({
    
     [`btn-${
      
      buttonType}`]: true });

Utilisé dans React :
Le code suivant implémente un composant bouton avec des fonctions interactives. Le nom de la classe de style du bouton changera dynamiquement en fonction de l'état du bouton, obtenant ainsi l'effet de retour de la pression sur le bouton et du survol de la souris.

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 (
		<button
			className={
    
    btnClass}
			onMouseDown={
    
    () => setIsPressed(true)}
			onMouseUp={
    
    () => setIsPressed(false)}
			onMouseEnter={
    
    () => setIsHovered(true)}
			onMouseLeave={
    
    () => setIsHovered(false)}
		>
			{
    
    props.label}
		</button>
	);
}

Et utilisez classnamesla bibliothèque pour générer dynamiquement le nom de classe du bouton :

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 (
		<button
			className={
    
    btnClass}
			onMouseDown={
    
    () => setIsPressed(true)}
			onMouseUp={
    
    () => setIsPressed(false)}
			onMouseEnter={
    
    () => setIsHovered(true)}
			onMouseLeave={
    
    () => setIsHovered(false)}
		>
			{
    
    props.label}
		</button>
	);
}
  • 'btn: true':La clé est btn, indiquant que le bouton doit contenir le nom de la classe btn.
  • 'btn-pressed': isPressed: la clé est btn-pressed, indiquant que lorsque isPressedc'est le castrue , le bouton doit contenir le nom de la classe btn-pressed.
  • 'btn-over': !isPressed && isHovered: La clé est btn-over, indiquant que lorsque isPressedis falseet isHoveredistrue , le bouton doit contenir le nom de la classe btn-over.

Étant donné que vous pouvez mélanger des paramètres d'objet, de tableau et de chaîne, la prise en charge des propriétés facultatives className propest également plus simple, puisque seuls les paramètres réels sont inclus dans le résultat :

const btnClass = classNames('btn', this.props.className, {
    
    
	'btn-pressed': isPressed,
	'btn-over': !isPressed && isHovered,
});

De plus, l'auteur propose deux autres versions : dedupeversion et bindversion.

La dedupeversion Where déduplique correctement les classes et garantit que les classes parasites spécifiées dans les paramètres suivants sont exclues du jeu de résultats. Cependant cette version est plus lente (environ 5x), il s'agit donc d'une version optionnelle.

const classNames = require('classnames/dedupe');

classNames('foo', 'foo', 'bar'); // => 'foo bar'
classNames('foo', {
    
     foo: false, bar: true }); // => 'bar'

Une autre bindversion vous permet de combiner css-modulespour ajouter ou supprimer dynamiquement des noms de classes CSS des composants tout en conservant css-modulesla portée.

css-modulesEst un moyen d'utiliser du CSS de portée locale dans votre projet. Il garantit que les noms de classe sont uniques dans l'ensemble de l'application en ajoutant une valeur de hachage unique à chaque nom de classe, évitant ainsi les conflits de noms de classe de portée globale.

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'

Voici un exemple d'utilisation d'une classnamescombinaison de bindversions 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>;
}

Interprétation du code source

Le code étant relativement court, le code source est publié directement ici avec des commentaires ajoutés pour que les lecteurs puissent le lire eux-mêmes :

indice

/*!
	Copyright (c) 2018 Jed Watson.
	Licensed under the MIT License (MIT), see
	http://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.exports
	if (typeof module !== 'undefined' && module.exports) {
    
    
		classNames.default = classNames;
		module.exports = classNames;
	} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
    
    
		// 如果在AMD环境下,则将classnames函数注册为模块,并将其命名为'classnames'
		define('classnames', [], function () {
    
    
			return classNames;
		});
	} else {
    
    
		// 在浏览器环境下,将classnames函数挂载到全局的window对象上
		window.classNames = classNames;
	}
}());

dédoublonner

/*!
	Copyright (c) 2018 Jed Watson.
	Licensed under the MIT License (MIT), see
	http://jedwatson.github.io/classnames
*/
/* global define */

(function () {
    
    
	'use strict';

	var classNames = (function () {
    
    
		// 创建一个不继承自Object的空对象,以便后面可以跳过hasOwnProperty的检查
		function StorageObject() {
    
    }
		StorageObject.prototype = Object.create(null);
		
		// 解析数组,将数组中的每个元素解析为classNames
		function _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-misconceptions
					resultSet[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 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);
			}
		}

		// 主函数
		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.exports
	if (typeof module !== 'undefined' && module.exports) {
    
    
		classNames.default = classNames;
		module.exports = classNames;
	} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
    
    
		// 如果在AMD环境下,则将classnames函数注册为模块,并将其命名为'classnames'
		define('classnames', [], function () {
    
    
			return classNames;
		});
	} else {
    
    
		// 在浏览器环境下,将classnames函数挂载到全局的window对象上
		window.classNames = classNames;
	}
}());

lier

/*!
	Copyright (c) 2018 Jed Watson.
	Licensed under the MIT License (MIT), see
	http://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.exports
	if (typeof module !== 'undefined' && module.exports) {
    
    
		classNames.default = classNames;
		module.exports = classNames;
	} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
    
    
		// 如果在AMD环境下,则将classnames函数注册为模块,并将其命名为'classnames'
		define('classnames', [], function () {
    
    
			return classNames;
		});
	} else {
    
    
		// 在浏览器环境下,将classnames函数挂载到全局的window对象上
		window.classNames = classNames;
	}
}());

Par rapport à index.js, cette version ajoute thisun traitement contextuel.

déclaration de type

// 以下类型声明主要用于定义一个名为 "classNames" 的命名空间和相关的类型。
// 在这个声明中,"classNames" 命名空间中定义了一些类型和接口
declare namespace classNames {
    
    
	// "Value" 是一个联合类型,表示可以接受的值的类型,包括字符串、数字、布尔值、未定义和空值
  type Value = string | number | boolean | undefined | null;
	// "Mapping" 是一个类型别名,表示一个键值对的集合,其中键是字符串,值可以是任何类型
  type Mapping = Record<string, unknown>;
	// "ArgumentArray" 是一个接口,继承自数组类型 "Array<Argument>",表示一个参数数组,其中每个元素都是 "Argument" 类型
  interface ArgumentArray extends Array<Argument> {
    
    }
	// "ReadonlyArgumentArray" 是一个接口,继承自只读数组类型 "ReadonlyArray<Argument>",表示一个只读的参数数组
  interface ReadonlyArgumentArray extends ReadonlyArray<Argument> {
    
    }
	// "Argument" 是一个联合类型,表示可以作为参数的类型,可以是 "Value"、"Mapping"、"ArgumentArray" 或 "ReadonlyArgumentArray"
  type 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;

Apprendre et gagner

  1. Utiliser le mode strict

La principale raison d’utiliser le mode strict au début de votre code source est de garantir la qualité et la fiabilité de votre code. Le mode strict peut aider les développeurs à éviter certaines erreurs courantes et une syntaxe irrégulière, tout en fournissant également une vérification des erreurs plus stricte et des invites d'erreur plus claires. L'utilisation du mode strict peut réduire certains dangers cachés et améliorer la maintenabilité et la lisibilité du code.

De plus, le mode strict peut également interdire certains comportements potentiellement dangereux, comme interdire l'utilisation de variables non déclarées, interdire l'affectation d'attributs en lecture seule, interdire la suppression de variables, etc. Cela peut améliorer la sécurité de votre code et réduire certaines vulnérabilités potentielles et risques de sécurité.

Par conséquent, afin de garantir la qualité, la fiabilité et la sécurité du code, de nombreux développeurs choisissent d'utiliser le mode strict au début du code source. Cela oblige le code à se conformer à des spécifications plus strictes, réduit les erreurs et les problèmes potentiels et améliore la maintenabilité et la lisibilité du code.

  1. Créer un objet vide qui n'hérite pas d'Object

Object.create(null)C'est une méthode qui crée un nouvel objet. L'objet n'a pas de chaîne de prototypes, c'est-à-dire qu'il n'hérite d'aucune propriété ou méthode. Cela signifie que l'objet n'a pas de propriétés ni de méthodes intégrées et ne peut être ajouté que par affectation directe. Les objets créés à l'aide de Object.create(null)sont appelés « objets purs » ou « objets de dictionnaire » et conviennent aux scénarios qui nécessitent une collection pure de paires clé-valeur sans héritage. Dans ce type d'objet, les clés et les valeurs peuvent être n'importe quel type de données, pas seulement des chaînes.

L'utilisation dans le code crée StorageObject.prototype = Object.create(null);un Objectobjet vide qui n'hérite pas de StorageObject. Cela peut ignorer hasOwnPropertyla vérification et améliorer les performances du code.

  1. Analyser différents types de paramètres, notamment des chaînes, des tableaux, des objets et des nombres

  2. Modèles de conception

Le modèle singleton est un modèle de conception créationnel qui garantit qu'il n'existe qu'une seule instance d'une classe et fournit un point d'accès global pour accéder à cette instance.

  • Mode Singleton : enveloppez le code en exécutant la fonction immédiatement, créez un classNamesobjet à l'intérieur de la fonction en cours d'exécution et affectez-le à la variable globale window.classNames. Cela garantit qu'un seul classNamesobjet existe et qu'aucun nouvel classNamesobjet ne peut être créé ailleurs.

Le modèle d'usine est un modèle de conception de création qui fournit une interface pour créer des objets, mais le type d'objet spécifique créé peut être déterminé au moment de l'exécution. Le modèle d'usine peut être divisé en modèle d'usine simple, modèle de méthode d'usine et modèle d'usine abstrait.

  1. Modèle de fabrique simple : également appelé modèle de fabrique statique, il utilise directement une méthode statique pour créer des objets.
  2. Modèle de méthode d'usine : également connu sous le nom de modèle d'usine virtuelle, il définit une interface d'usine et crée différents objets par différentes implémentations d'usine spécifiques.
  • _classNames()Modèle d'usine : créez un objet via une fonction d'usine classNames, qui peut appeler différentes fonctions d'analyse pour analyser les paramètres en fonction de différents types de paramètres et stocker les résultats dans classSetl'objet.
  1. Déterminer l'environnement d'exécution et exporterclassNames

Selon les différents environnements d'exploitation, déterminez s'il s'agit de CommonJSl'environnement, AMDde l'environnement ou de l'environnement du navigateur. Si dans CommonJSl'environnement, classNamesattribuez-le à module.exports; si dans AMDl'environnement, classNamesenregistrez-le en tant que module et nommez-le 'classnames'; si dans l'environnement du navigateur, classNamesmontez sur l' windowobjet global.

  1. Déclaration de type TypeScript
  • Déclaration d'espace de noms : permet declare namespacede définir un espace de noms, d'organiser les types et les interfaces associés, d'éviter les conflits de noms et de fournir une structure modulaire.
  • Alias ​​de type et types d'union : utilisez typele mot-clé pour définir des alias de type afin de faciliter la réutilisation de types complexes. Les types d’union peuvent être utilisés pour représenter qu’une valeur peut appartenir à plusieurs types différents.
  • Interface et héritage : utilisez interfacele mot-clé pour définir une interface, qui représente la structure d'un objet. Les interfaces peuvent hériter d'autres interfaces et les définitions d'interface existantes peuvent être réutilisées via l'héritage.
  • Type de fonction : vous pouvez utiliser des interfaces pour définir des types de fonctions et spécifier les types de paramètres et les types de valeurs de retour de la fonction.
  • Exportation de type et importation de module : utilisez exportle mot-clé pour exporter un type ou une valeur afin qu'il puisse être utilisé dans d'autres modules. importLes types ou valeurs exportés peuvent être importés dans d'autres modules à l'aide du mot-clé.

Je suppose que tu aimes

Origine blog.csdn.net/p1967914901/article/details/132023841
conseillé
Classement