源码阅读:classnames

源码阅读:classnames

  • 源码阅读:classnames
    • 简介
    • 源码解读
      • index
      • dedupe
      • bind
      • 类型声明
    • 学习与收获

源码阅读:classnames

简介

classnames 一个简单的 JavaScript 实用程序,用于有条件地将类名连接在一起。

可以通过 npm 包管理器从 npm 注册表上下载:

npm install classnames

classNames 函数接受任意数量的参数,可以是字符串或对象。参数 '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 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 });

在 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,表示当isPressedtrue时,按钮应该包含类名btn-pressed
  • 'btn-over': !isPressed && isHovered:键为btn-over,表示当isPressedfalseisHoveredtrue时,按钮应该包含类名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'

下面是一个使用classnamesbind版本结合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函数注册为模块,并将其命名为'classnames'define('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 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.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函数注册为模块,并将其命名为'classnames'define('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函数注册为模块,并将其命名为'classnames'define('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 = 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;

学习与收获

  1. 使用严格模式

在源码开头使用严格模式的主要原因是为了确保代码的质量和可靠性。严格模式可以帮助开发者避免一些常见的错误和不规范的语法,同时也提供了更严格的错误检查和更清晰的错误提示。使用严格模式可以减少一些隐患,提高代码的可维护性和可读性。

此外,严格模式还可以禁止一些潜在的危险行为,例如禁止使用未声明的变量、禁止对只读属性赋值、禁止删除变量等。这可以提高代码的安全性,减少一些潜在的漏洞和安全风险。

因此,为了确保代码的质量、可靠性和安全性,许多开发者选择在源码开头使用严格模式。这样可以强制要求代码符合更严格的规范,减少错误和潜在的问题,并提高代码的可维护性和可读性。

  1. 创建一个不继承自 Object 的空对象

Object.create(null) 是一个创建一个新对象的方法,该对象没有原型链,也就是没有继承任何属性和方法。这意味着该对象没有内置的属性和方法,只能通过直接赋值来添加属性和方法。使用 Object.create(null) 创建的对象被称为“纯净对象”或“字典对象”,它适用于需要一个纯粹的键值对集合而不需要继承的场景。在这种对象中,键和值可以是任何类型的数据,而不仅限于字符串。

在代码中使用了 StorageObject.prototype = Object.create(null); 创建了一个不继承自 Object 的空对象 StorageObject。这样可以跳过 hasOwnProperty 的检查,提高代码的性能。

  1. 解析不同类型的参数,包括字符串、数组、对象和数字

  2. 设计模式

单例模式是一种创建型设计模式,用于确保某个类只有一个实例,并提供一个全局访问点来访问该实例。

  • 单例模式:通过立即执行函数包裹代码,在执行函数内部创建了一个classNames对象,并将其赋值给全局变量window.classNames。这样就保证了只有一个classNames对象存在,其他地方无法再创建新的classNames对象。

工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但具体创建的对象类型可以在运行时确定。工厂模式可以分为简单工厂模式、工厂方法模式和抽象工厂模式。

  1. 简单工厂模式:也称为静态工厂模式,它直接使用一个静态方法来创建对象。
  2. 工厂方法模式:也称为虚拟工厂模式,它定义了一个工厂接口,并由不同的具体工厂实现来创建不同的对象。
  • 工厂模式:通过工厂函数_classNames()创建classNames对象,该对象可以根据不同的参数类型调用不同的解析函数来解析参数,并将结果存储在classSet对象中。
  1. 判断运行环境并导出 classNames

根据不同的运行环境,判断是否在 CommonJS 环境下、AMD 环境下或浏览器环境下。如果在 CommonJS 环境下,将 classNames 赋值给 module.exports;如果在 AMD 环境下,将 classNames 注册为模块,并命名为 'classnames';如果在浏览器环境下,将 classNames 挂载到全局的 window 对象上。

  1. TypeScript 类型声明
  • 命名空间声明:使用declare namespace可以定义一个命名空间,将相关的类型和接口组织在一起,防止命名冲突并提供模块化的结构。
  • 类型别名和联合类型:使用 type 关键字可以定义类型别名,方便重复使用复杂的类型。联合类型可以用于表示一个值可以是多个不同类型之一。
  • 接口和继承:使用 interface 关键字可以定义接口,表示一种对象的结构。接口可以继承自其他接口,通过继承可以复用已有的接口定义。
  • 函数类型:可以使用接口来定义函数类型,指定函数的参数类型和返回值类型。
  • 类型导出和模块导入:使用 export 关键字可以将类型或值导出,使其可以在其他模块中使用。使用 import 关键字可以在其他模块中导入已导出的类型或值。

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

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

相关文章

【YOLO】模型推理(trt和engine)

【YOLO】模型推理(trt和engine) 上一章 yolov5+TensorRT推理 文章目录 【YOLO】模型推理(trt和engine)前言一、pt转onnx模型1.1 下载yolov51.2 模型导出二、Netron Viewer安装三、onnx转trt模型3.1 安装TensorRT3.2 模型转换四、执行推理4.1 推理五、模型量化FP16\INT85.1 性能…

05-向量的意义_n维欧式空间

线性代数 什么是向量&#xff1f;究竟为什么引入向量&#xff1f; 为什么线性代数这么重要&#xff1f;从研究一个数拓展到研究一组数 一组数的基本表示方法——向量&#xff08;Vector&#xff09; 向量是线性代数研究的基本元素 e.g. 一个数&#xff1a; 666&#xff0c;…

leetcode算法题--统计完全子数组的数目

原题链接&#xff1a;https://leetcode.cn/problems/count-complete-subarrays-in-an-array/ 一开始的做法比较简单粗暴&#xff0c;复杂度是O(n*n) func countCompleteSubarrays(nums []int) int {cnt1 : make(map[int]int)for _, num : range nums {cnt1[num] }count : len…

springboot整合tio-websocket方案实现简易聊天

写在最前&#xff1a; 常用的http协议是无状态的&#xff0c;且不能主动响应到客户端。最初想实现状态动态跟踪只能用轮询或者其他效率低下的方式&#xff0c;所以引入了websocket协议&#xff0c;允许服务端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器和服务…

实用上位机--QT

实用上位机–QT 通信协议如下 上位机设计界面 #------------------------------------------------- # # Project created by QtCreator 2023-07-29T21:22:32 # #-------------------------------------------------QT += core gui serialportgreaterThan(QT_MAJOR_V…

Unity实现在3D模型标记

Canvas 模式是UI与3D混合模式&#xff08;Render modelScreen space-Camera) 实现在3D模型标记&#xff0c;旋转跟随是UI不在3D物体下 代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public clas…

矩阵中的路径(JS)

矩阵中的路径 题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是…

valgrind——内存泄漏检测介绍

文章目录 1. 概述1. 体系结构2. Linux 程序内存空间布局3. 内存检查原理 2. valgrind工具3. 常用选项4. 示例1. 内存泄漏2. 数组越界3. 内存覆盖4. 使用未初始化的值5. 内存申请与释放函数不匹配 5. 总结 1. 概述 1. 体系结构 Valgrind 是一套 Linux 下&#xff0c;开放源代码…

移动端个人中心UI设计

效果图 源码如下 页面设计 <template><div class"container"><!-- 顶部用户信息 start--><div class"header"><div class"user-info"><van-image class"user-img" round width"70" :sr…

Idea 2023.2 maven 打包时提示 waring 问题解决

Version idea 2023.2 问题 使用 Maven 打包 &#xff0c;控制台输出 Waring 信息 [WARNING] [WARNING] Plugin validation issues were detected in 7 plugin(s) [WARNING] [WARNING] * org.apache.maven.plugins:maven-dependency-plugin:3.3.0 [WARNING] * org.apache.…

HCIP--云计算题库 V5.0版本

在国家政策的支持下&#xff0c;我国云计算应用市场发展明显加快&#xff0c;越来越多的企业开始介入云产业&#xff0c;出现了大量的应用解决方案&#xff0c;云应用的成功案例逐渐丰富&#xff0c;用户了解和认可程度不断提高&#xff0c;云计算产业发展迎来了“黄金机遇期”…

【ArcGIS Pro二次开发】(55):给多个要素或表批量添加字段

在工作中可能会遇到这样的场景&#xff1a;有多个GDB要素、表格&#xff0c;或者是SHP文件&#xff0c;需要给这个要素或表添加相同的多个字段。 在这种情况下&#xff0c;手动添加就变得很繁琐&#xff0c;于是就做了这个工具。 需求具体如下图&#xff1a; 左图是待处理数据…

C数据结构——无向图(邻接矩阵方式) 创建与基本使用

源码注释 // // Created by Lenovo on 2022-05-13-上午 9:06. // 作者&#xff1a;小象 // 版本&#xff1a;1.0 //#include <stdio.h> #include <malloc.h>#define MAXSIZE 1000 // BFS队列可能达到的最大长度 #define MAX_AMVNUMS 100 // 最大顶点数typedef enu…

电脑剪辑视频的软件有哪些?试试这几种视频剪辑工具

视频剪辑可以帮助人们在不同情境下更好地理解和消化视频内容。通过剪辑&#xff0c;可以去除不必要的素材并突出重点&#xff0c;使观看者能够更快地获取信息&#xff0c;并且更容易保持注意力的集中。此外&#xff0c;剪辑可以提高视频质量&#xff0c;例如通过添加音乐、图形…

【shell脚本】shell脚本之日志切割(进阶实战三)

恭喜你&#xff0c;找到宝藏博主了&#xff0c;这里会分享shell的学习整过程。 shell 对于运维来说是必备技能之一&#xff0c;它可以提高很多运维重复工作&#xff0c;提高效率。 shell的专栏&#xff0c;我会详细地讲解shell的基础和使用&#xff0c;以及一些比较常用的she…

CVPR2023新作:源数据集对迁移学习性能的影响以及相应的解决方案

Title: A Data-Based Perspective on Transfer Learning (迁移学习的基于数据的观点) Affiliation: MIT (麻省理工学院) Authors: Saachi Jain, Hadi Salman, Alaa Khaddaj, Eric Wong, Sung Min Park, Aleksander Mądry Keywords: transfer learning, source dataset, dow…

Git分布式版本控制工具和GitHub(二)--Git指令入门

一.指令入门前的准备 1.Git全局设置 2.获取Git仓库 例如&#xff1a;将我GitHub上的first_resp仓库克隆到本地。 点击进入first_rep&#xff0c;后面本地仓库操作的学习就是在这个界面右键打开Git Bash 3.工作区&#xff0c;暂存区&#xff0c;版本库概念 注&#xff1a;如果空…

Reinforcement-Learning

文章目录 Reinforcement-Learning1. RL方法分类汇总&#xff1a;2. Q-Learning3. SARSA算法4. SARSA&#xff08;λ&#xff09; Reinforcement-Learning 1. RL方法分类汇总&#xff1a; &#xff08;1&#xff09;不理解环境&#xff08;Model-Free RL&#xff09;&#xff…

【【51单片机的红外遥控】】

红外遥控&#xff0c;完全把控 红外遥控 利用红外光进行通信的设备&#xff0c;由红外LED将调制后的信号发出&#xff0c;再由专门的红外接收头进行解调输出 通信方式&#xff1a;单工 异步 红外LED波长&#xff1a;940nm 通信协议标准&#xff1a;NEC标准 用那种一体化红红外…

【MySQL】模具数据转移处理

系列文章 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类&#xff08;推荐阅读&#xff0…