Lectura del código fuente: nombres de clases

Lectura del código fuente: nombres de clases

Introducción

classnamesUna sencilla utilidad de JavaScript para concatenar condicionalmente nombres de clases.

Se puede descargar desde el registro a través npmdel administrador de paquetes npm:

npm install classnames

classNamesLas funciones aceptan cualquier número de argumentos, que pueden ser cadenas u objetos. El parámetro 'foo'es la abreviatura de {foo: true}. Si el valor asociado con una clave determinada es falso, la clave no se incluirá en la salida.

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 });

Utilizado en React:
el siguiente código implementa un componente de botón con funciones interactivas. El nombre de la clase de estilo del botón cambiará dinámicamente según el estado del botón, logrando así el efecto de retroalimentación al presionar el botón y pasar el mouse.

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>
	);
}

Y use classnamesla biblioteca para generar dinámicamente el nombre de clase del botón:

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 clave es btn, lo que indica que el botón debe contener el nombre de la clase btn.
  • 'btn-pressed': isPressed: clave es btn-pressed, indicando que cuando isPressedestrue , el botón debe contener el nombre de la clase btn-pressed.
  • 'btn-over': !isPressed && isHovered: La clave es btn-over, indica que cuando isPressedes falsey isHoveredestrue , el botón debe contener el nombre de la clase btn-over.

Debido a que puede mezclar parámetros de objeto, matriz y cadena, admitir className proppropiedades opcionales también es más sencillo, ya que solo se incluyen los parámetros reales en el resultado:

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

Además, el autor aporta otras dos versiones: dedupeversión y bindversión.

La dedupeversión donde deduplica correctamente las clases y garantiza que las clases espurias especificadas en los siguientes parámetros se excluyan del conjunto de resultados. Sin embargo, esta versión es más lenta (aproximadamente 5 veces), por lo que es una versión opcional.

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

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

Otra bindversión le permite combinar css-modulespara agregar o eliminar dinámicamente nombres de clases CSS de los componentes manteniendo css-modulesel alcance.

css-modulesEs una forma de utilizar CSS de ámbito local en su proyecto. Garantiza que los nombres de clases sean únicos en toda la aplicación agregando un valor hash único a cada nombre de clase, evitando conflictos de nombres de clases de alcance global.

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'

A continuación se muestra un ejemplo del uso de una classnamescombinación de bindversiones 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>;
}

Interpretación del código fuente

Dado que el código es relativamente corto, el código fuente se publica directamente aquí con comentarios agregados para que los lectores lo lean por sí mismos:

índice

/*!
	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;
	}
}());

deduplicar

/*!
	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;
	}
}());

unir

/*!
	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;
	}
}());

En comparación con index.js, esta versión agrega thisprocesamiento de contexto.

declaración de tipo

// 以下类型声明主要用于定义一个名为 "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;

Aprende y gana

  1. Usar modo estricto

La razón principal para utilizar el modo estricto al comienzo de su código fuente es garantizar la calidad y confiabilidad de su código. El modo estricto puede ayudar a los desarrolladores a evitar algunos errores comunes y sintaxis irregular, al mismo tiempo que proporciona una verificación de errores más estricta y avisos de error más claros. El uso del modo estricto puede reducir algunos peligros ocultos y mejorar la capacidad de mantenimiento y legibilidad del código.

Además, el modo estricto también puede prohibir algunos comportamientos potencialmente peligrosos, como prohibir el uso de variables no declaradas, prohibir la asignación de atributos de solo lectura, prohibir la eliminación de variables, etc. Esto puede mejorar la seguridad de su código y reducir algunas vulnerabilidades y riesgos de seguridad potenciales.

Por lo tanto, para garantizar la calidad, confiabilidad y seguridad del código, muchos desarrolladores optan por utilizar el modo estricto al comienzo del código fuente. Esto obliga al código a ajustarse a especificaciones más estrictas, reduce errores y problemas potenciales y mejora la facilidad de mantenimiento y legibilidad del código.

  1. Crea un objeto vacío que no hereda de Object

Object.create(null)Es un método que crea un nuevo objeto, el objeto no tiene cadena prototipo, es decir, no hereda ninguna propiedad ni método. Esto significa que el objeto no tiene propiedades ni métodos integrados y solo se puede agregar mediante asignación directa. Los objetos creados con Object.create(null)se denominan "objetos puros" u "objetos de diccionario" y son adecuados para escenarios que requieren una colección pura de pares clave-valor sin herencia. En este tipo de objetos, las claves y los valores pueden ser cualquier tipo de datos, no solo cadenas.

El uso en el código crea StorageObject.prototype = Object.create(null);un Objectobjeto vacío que no hereda de StorageObject. Esto puede omitir hasOwnPropertyla verificación y mejorar el rendimiento del código.

  1. Analizar diferentes tipos de argumentos, incluidas cadenas, matrices, objetos y números.

  2. Patrones de diseño

El patrón singleton es un patrón de diseño creacional que garantiza que solo haya una instancia de una clase y proporciona un punto de acceso global para acceder a esa instancia.

  • Modo singleton: ajuste el código ejecutando la función inmediatamente, cree un classNamesobjeto dentro de la función en ejecución y asígnelo a la variable global window.classNames. Esto garantiza que solo classNamesexista un objeto y que no classNamesse puedan crear nuevos objetos en ningún otro lugar.

El patrón de fábrica es un patrón de diseño creacional que proporciona una interfaz para crear objetos, pero el tipo de objeto específico creado se puede determinar en tiempo de ejecución. El patrón de fábrica se puede dividir en patrón de fábrica simple, patrón de método de fábrica y patrón de fábrica abstracto.

  1. Patrón de fábrica simple: también llamado patrón de fábrica estático, utiliza directamente un método estático para crear objetos.
  2. Patrón de método de fábrica: también conocido como patrón de fábrica virtual, define una interfaz de fábrica y crea diferentes objetos mediante diferentes implementaciones de fábrica específicas.
  • _classNames()Patrón de fábrica: cree un objeto a través de una función de fábrica classNames, que puede llamar a diferentes funciones de análisis para analizar parámetros de acuerdo con diferentes tipos de parámetros y almacenar los resultados en classSetel objeto.
  1. Determinar el entorno de ejecución y exportar.classNames

Según los diferentes entornos operativos, determine si está en CommonJSel entorno, AMDel entorno o el entorno del navegador. Si está en CommonJSel entorno, classNamesasígnelo a module.exports; si está en AMDel entorno, classNamesregístrelo como módulo y asígnele un nombre 'classnames'; si está en el entorno del navegador, classNamesmonte en el windowobjeto global.

  1. Declaración de tipo TypeScript
  • Declaración de espacio de nombres: se utiliza declare namespacepara definir un espacio de nombres, organizar tipos e interfaces relacionados juntos, evitar conflictos de nombres y proporcionar una estructura modular.
  • Alias ​​de tipos y tipos de unión: utilice typela palabra clave para definir alias de tipos para facilitar la reutilización de tipos complejos. Los tipos de unión se pueden utilizar para representar que un valor puede ser uno de varios tipos diferentes.
  • Interfaz y herencia: utilice interfacela palabra clave para definir una interfaz, que representa la estructura de un objeto. Las interfaces pueden heredar de otras interfaces y las definiciones de interfaces existentes se pueden reutilizar mediante herencia.
  • Tipo de función: puede utilizar interfaces para definir tipos de funciones y especificar los tipos de parámetros y los tipos de valores de retorno de la función.
  • Exportación de tipos e importación de módulos: use exportla palabra clave para exportar un tipo o valor para que pueda usarse en otros módulos. importLos tipos o valores exportados se pueden importar en otros módulos usando la palabra clave.

Supongo que te gusta

Origin blog.csdn.net/p1967914901/article/details/132023841
Recomendado
Clasificación