探索原生JS的力量:自定义实现类似于React的useState功能

1.写在前面

本方案特别适合希望在历史遗留的原生JavaScript项目中实现简单轻量级数据驱动机制的开发者。无需引入任何框架或第三方库,即可按照此方法封装出类似于React中useState的功能,轻松为项目添加状态管理能力,既保持了项目的轻量性,又提升了开发效率;

追求轻量,推荐直接看7轻量版的实现!

2.优势总结  ★ 了解

1. 轻量级响应式系统

  • 无虚拟DOM:直接监听状态变化并更新真实DOM,避免虚拟DOM的diff计算开销

  • 精准更新:只有订阅了状态变化的DOM元素会更新(相比React的组件级重渲染更精确)

2. 类React开发体验

  • 提供useState+setState的API设计,降低学习成本

  • 支持函数式更新:setState(prev => prev + 1)

3. 状态不可变性

  • 自动深拷贝状态,避免意外修改

  • 每次更新都生成新状态,便于实现时间旅行调试

4. 批量更新优化

  • batch()可合并多次更新为单次渲染

  • 避免频繁DOM操作导致的布局抖动

5. 多实例隔离

  • 不同模块可以使用独立的状态实例,避免全局污染

3.单例模式 ★ 重要

单例模式效果展示

单例模式封装

/*** 单例模式状态管理* 整个应用共享同一个状态实例*/// ==================== 深拷贝工具 ====================
function deepClone(obj, hash = new WeakMap()) {if (obj == null) return obj;if (typeof obj !== 'object') return obj;const constructor = obj.constructor;const specialTypes = ['Date', 'RegExp', 'Map', 'Set'];if (specialTypes.includes(constructor.name)) {return new constructor(obj);}if (hash.has(obj)) return hash.get(obj);const clone = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));hash.set(obj, clone);[...Object.getOwnPropertySymbols(obj), ...Object.keys(obj)].forEach(key => {clone[key] = deepClone(obj[key], hash);});return clone;
}// ==================== 核心实现 ====================
const subscribers = new Map();
let batchQueue = [];
let isBatching = false;function batchNotify(proxy) {const callbacks = subscribers.get(proxy);if (!callbacks) return;Promise.resolve().then(() => {callbacks.forEach(cb => {try {cb(proxy.value);} catch (e) {console.error('回调执行失败:', e);}});});
}export const useState = (initialState) => {if (typeof initialState === 'undefined') {throw new Error('初始状态不能为undefined');}const proxy = new Proxy({ value: deepClone(initialState) }, {set(target, key, value) {if (key !== 'value') return false;target[key] = deepClone(value);if (!isBatching) batchNotify(proxy);return true;}});return {get state() { return proxy.value; },setState: (updater) => {if (isBatching) {batchQueue.push({ proxy, updater });} else {proxy.value = typeof updater === 'function' ? updater(proxy.value) : updater;}},subscribe: (callback) => {if (typeof callback !== 'function') {throw new Error('回调必须是函数');}if (!subscribers.has(proxy)) {subscribers.set(proxy, new Set());}subscribers.get(proxy).add(callback);return () => {subscribers.get(proxy)?.delete(callback);};}};
};export const batch = (callback) => {if (isBatching) return callback();isBatching = true;batchQueue = [];try {callback();const updatesByProxy = new Map();batchQueue.forEach(({ proxy, updater }) => {if (!updatesByProxy.has(proxy)) {updatesByProxy.set(proxy, []);}updatesByProxy.get(proxy).push(updater);});updatesByProxy.forEach((updaters, proxy) => {let state = proxy.value;updaters.forEach(updater => {state = typeof updater === 'function' ? updater(state) : updater;state = deepClone(state);});proxy.value = state;batchNotify(proxy);});} finally {isBatching = false;batchQueue = [];}
};

 单例模式HTML测试

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>单例模式测试</title><script type="module">import { useState, batch } from './singleton-state.js';const counter = useState(0);counter.subscribe(count => {document.getElementById('count').textContent = count;console.log('当前计数:', count);});// 普通更新document.getElementById('increment').addEventListener('click', () => {counter.setState(c => c + 1);});// 批量更新document.getElementById('increment-5').addEventListener('click', () => {batch(() => {counter.setState(c => c + 1);counter.setState(c => c + 1);counter.setState(c => c + 1);counter.setState(c => c + 1);counter.setState(c => c + 1);});});</script>
</head>
<body>
<h1>单例模式测试</h1>
<div>计数: <span id="count">0</span></div>
<button id="increment">+1</button>
<button id="increment-5">+5 (批量)</button>
</body>
</html>

4.多例模式 ★ 重要

 双例模式效果展示

  双例模式封装

/*** 多例模式状态管理工具* 允许创建多个独立的状态管理实例,每个实例拥有独立的状态和订阅系统* * 主要特点:* 1. 可创建多个隔离的状态管理实例* 2. 每个实例拥有独立的useState和batch方法* 3. 完整的状态不可变性保证* 4. 支持批量更新优化性能* * 使用方式:* const store = createStateStore();* const counter = store.useState(0);* * counter.subscribe(state => console.log(state));* counter.setState(prev => prev + 1);* store.batch(() => { ... });*/// ==================== 深拷贝工具函数 ====================/*** 高性能深拷贝函数* @param {any} obj - 需要拷贝的对象* @param {WeakMap} [hash=new WeakMap()] - 用于存储已拷贝对象的WeakMap(防止循环引用)* @returns {any} 深拷贝后的对象* * 实现特点:* 1. 处理基本数据类型:直接返回* 2. 处理循环引用:使用WeakMap缓存已拷贝对象* 3. 保留特殊对象类型:Date、RegExp等* 4. 原型链继承:保持原型链关系* 5. 性能优化:使用Object.keys+Symbol属性遍历*/
function deepClone(obj, hash = new WeakMap()) {// 处理null和undefinedif (obj == null) return obj;// 处理基本数据类型(string, number, boolean, symbol, bigint)if (typeof obj !== 'object') return obj;// 处理特殊对象类型const constructor = obj.constructor;const specialTypes = ['Date', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet'];if (specialTypes.includes(constructor.name)) {return new constructor(obj);}// 检查循环引用if (hash.has(obj)) return hash.get(obj);// 根据对象类型创建空对象或数组const clone = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));// 缓存当前对象,防止循环引用hash.set(obj, clone);// 拷贝Symbol类型属性const symKeys = Object.getOwnPropertySymbols(obj);if (symKeys.length > 0) {symKeys.forEach(symKey => {clone[symKey] = deepClone(obj[symKey], hash);});}// 拷贝普通属性Object.keys(obj).forEach(key => {clone[key] = deepClone(obj[key], hash);});return clone;
}// ==================== 状态管理工厂函数 ====================/*** 创建新的状态管理实例* @returns {Object} 包含useState和batch方法的对象* * 每个实例包含:* 1. 独立的订阅者系统* 2. 独立的批量更新队列* 3. 独立的状态树*/
export function createStateStore() {/*** 订阅者集合* Map结构:* key: 状态代理对象(Proxy)* value: 该状态的订阅者回调集合(Set)*/const subscribers = new Map();/*** 批量更新队列* 数组结构,每个元素包含:* - proxy: 状态代理对象* - updater: 更新函数或值*/let batchQueue = [];/*** 批量更新标志* @type {boolean}*/let isBatching = false;// ==================== 内部工具方法 ====================/*** 通知订阅者状态变更* @param {Proxy} proxy - 状态代理对象* * 实现特点:* 1. 使用微任务(Promise)异步执行通知* 2. 错误处理避免影响其他订阅者* 3. 自动清理无效订阅*/function batchNotify(proxy) {// 获取当前状态的所有订阅者const callbacks = subscribers.get(proxy);if (!callbacks || callbacks.size === 0) return;// 使用微任务异步执行通知Promise.resolve().then(() => {// 获取当前状态值const state = proxy.value;// 遍历执行所有订阅回调callbacks.forEach(callback => {try {callback(state);} catch (error) {console.error('[状态通知错误] 订阅回调执行失败:', error);}});});}// ==================== 公开API ====================/*** 创建响应式状态* @param {any} initialState - 初始状态* @returns {Object} 包含state、setState和subscribe方法的对象* * @throws {Error} 当initialState为undefined时抛出错误*/function useState(initialState) {// 参数校验if (typeof initialState === 'undefined') {throw new Error('useState: 初始状态不能为undefined');}// 创建响应式代理对象const proxy = new Proxy({ value: deepClone(initialState) },{/*** 代理set陷阱* @param {Object} target - 目标对象* @param {string} key - 属性名* @param {any} value - 新值* @returns {boolean} 是否设置成功*/set(target, key, value) {// 只处理value属性的变更if (key !== 'value') return false;// 深拷贝新值,确保状态不可变target[key] = deepClone(value);// 非批量模式下立即通知订阅者if (!isBatching) {batchNotify(proxy);}return true;}});/*** 订阅状态变更* @param {Function} callback - 状态变更回调函数* @returns {Function} 取消订阅的函数* * @throws {Error} 当callback不是函数时抛出错误*/function subscribe(callback) {// 参数校验if (typeof callback !== 'function') {throw new Error('subscribe: 回调必须是函数');}// 初始化该状态的订阅者集合if (!subscribers.has(proxy)) {subscribers.set(proxy, new Set());}// 添加订阅者const callbacks = subscribers.get(proxy);callbacks.add(callback);// 返回取消订阅函数return function unsubscribe() {callbacks.delete(callback);// 清理空订阅集合if (callbacks.size === 0) {subscribers.delete(proxy);}};}/*** 更新状态* @param {Function|any} updater - 更新函数或新状态值* * 更新规则:* 1. 如果是函数:updater(prevState) => newState* 2. 如果是值:直接替换状态*/function setState(updater) {if (isBatching) {// 批量模式下将更新操作加入队列batchQueue.push({proxy,updater});} else {// 直接更新模式proxy.value = typeof updater === 'function'? updater(proxy.value): updater;}}// 返回状态访问接口return {/*** 获取当前状态值* @returns {any} 当前状态*/get state() {return proxy.value;},setState,subscribe};}/*** 批量更新状态* @param {Function} callback - 包含多个状态更新的回调函数* * 实现特点:* 1. 合并多个setState调用为一次更新* 2. 自动处理状态依赖关系* 3. 最终只触发一次订阅通知*/function batch(callback) {// 如果已经在批量模式中,直接执行回调if (isBatching) {callback();return;}// 进入批量模式isBatching = true;batchQueue = [];try {// 执行用户回调,收集所有setState操作callback();// 按状态代理分组更新操作const updatesByProxy = new Map();batchQueue.forEach(({ proxy, updater }) => {if (!updatesByProxy.has(proxy)) {updatesByProxy.set(proxy, []);}updatesByProxy.get(proxy).push(updater);});// 处理每个状态代理的更新updatesByProxy.forEach((updaters, proxy) => {let currentState = proxy.value;// 按顺序应用所有更新器updaters.forEach(updater => {currentState = typeof updater === 'function'? updater(currentState): updater;// 确保每次更新都是不可变的currentState = deepClone(currentState);});// 最终更新状态值proxy.value = currentState;// 通知该状态的订阅者batchNotify(proxy);});} finally {// 确保无论是否出错都退出批量模式isBatching = false;batchQueue = [];}}// 返回实例方法return { useState, batch };
}// ==================== 可选默认实例 ====================/*** 默认导出的状态管理实例* 为方便使用,同时提供创建新实例和默认实例两种方式*/
const defaultStore = createStateStore();
export const { useState: defaultUseState, batch: defaultBatch } = defaultStore;

  双例模式HTML测试

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>多例模式状态管理测试</title><style>body {font-family: Arial, sans-serif;max-width: 600px;margin: 0 auto;padding: 20px;}.counter {margin: 20px 0;padding: 15px;border: 1px solid #ddd;border-radius: 5px;}button {padding: 8px 16px;margin-right: 10px;cursor: pointer;}</style>
</head>
<body><h1>多例模式状态管理测试</h1><div class="counter"><h2>独立计数器1</h2><div>当前值: <span id="counter1-value">0</span></div><button id="counter1-increment">增加</button><button id="counter1-batch">批量增加(+3)</button></div><div class="counter"><h2>独立计数器2</h2><div>当前值: <span id="counter2-value">100</span></div><button id="counter2-increment">增加</button></div><script type="module">// 从模块导入创建方法import { createStateStore } from './multi-state.js';// 创建两个完全独立的状态管理实例const store1 = createStateStore();const store2 = createStateStore();// 实例1:计数器1const counter1 = store1.useState(0);counter1.subscribe(state => {document.getElementById('counter1-value').textContent = state;console.log('计数器1更新:', state);});document.getElementById('counter1-increment').addEventListener('click', () => {counter1.setState(prev => prev + 1);});document.getElementById('counter1-batch').addEventListener('click', () => {store1.batch(() => {counter1.setState(prev => prev + 1);counter1.setState(prev => prev + 1);counter1.setState(prev => prev + 1);});});// 实例2:计数器2 (完全独立)const counter2 = store2.useState(100);counter2.subscribe(state => {document.getElementById('counter2-value').textContent = state;console.log('计数器2更新:', state);});document.getElementById('counter2-increment').addEventListener('click', () => {counter2.setState(prev => prev + 10);});// 暴露到全局方便测试window.stores = { store1, store2, counter1, counter2 };</script><div style="margin-top: 30px; color: #666;"><h3>测试说明:</h3><p>1. 两个计数器使用完全独立的状态管理实例</p><p>2. 打开控制台可以查看状态变化日志</p><p>3. 在控制台输入 <code>stores</code> 可以访问状态实例</p></div>
</body>
</html>

5.单例模式和双例模式的区别  ★ 了解

  1. 单例模式

    • 全局共享一个状态树

    • 直接导出 useState 和 batch

    • 适合中小型应用

  2. 多例模式

    • 通过 createStateStore() 创建独立实例

    • 每个实例有自己的状态和订阅系统

    • 适合大型应用或需要隔离状态的场景


6.兼容性分析   ★ 了解

1. 支持的浏览器

特性

最低支持版本

覆盖率

Proxy

Chrome 49+

~98%

Firefox 18+

Edge 12+

Safari 10+

WeakMap

IE 11+

~99%

Promise (微任务)

ES6+

~98%

2. 不兼容场景

  • IE 11及以下:不支持Proxy(可用Object.defineProperty降级)

  • 老旧移动浏览器:部分Android 4.x设备不支持ES6

3. Polyfill方案

// 在入口文件添加以下polyfill
import 'core-js/stable';  // 提供Promise/WeakMap等
import 'proxy-polyfill';  // 提供Proxy的简单实现

7.轻量版  ★ 推荐

7.1 核心机制说明:

  1. 依赖收集 (track)

    • 当读取 state.value 时,如果存在 activeEffect,会将这个 effect 收集起来

    • 存储结构:WeakMap<target, Map<key, Set<effect>>>

  2. 触发更新 (trigger)

    • 当设置 state.value 时,会查找并执行所有收集到的 effect

  3. 响应式循环

    • 初始化时执行 updateDOM 会触发 getter 收集依赖

    • 当值变化时触发 setter,执行所有依赖的 effect(包括 updateDOM

  4. 双向绑定

    • 自动为 input 元素添加 input 事件监听

    • 输入变化时更新状态,状态变化时更新输入框值

  5. 清理机制

    • 每个绑定元素存储了自己的清理函数

    • 可以在不需要时移除所有事件监听,防止内存泄漏

7.2 封装的js文件

// 使用 WeakMap 来存储目标对象与其依赖映射的关系
// WeakMap 的键是对象,值是一个 Map,用于存储该对象属性的依赖集合
const targetMap = new WeakMap();// 当前正在运行的 effect(副作用函数),用于依赖收集
let activeEffect = null;// 唯一ID计数器,用于为没有指定标识符的状态生成唯一ID
let uid = 0;/*** 跟踪依赖关系,建立属性与 effect 之间的联系* @param {object} target - 目标对象(被代理的对象)* @param {string|symbol} key - 被访问的属性名*/
function track(target, key) {// 如果没有活跃的 effect,直接返回(不需要收集依赖)if (!activeEffect) return;// 从 targetMap 中获取目标对象的依赖映射let depsMap = targetMap.get(target);// 如果没有依赖映射,则为该目标对象创建一个新的 Map 并存入 targetMapif (!depsMap) targetMap.set(target, (depsMap = new Map()));// 获取该属性对应的依赖集合let dep = depsMap.get(key);// 如果没有依赖集合,则为该属性创建一个新的 Set 并存入 depsMapif (!dep) depsMap.set(key, (dep = new Set()));// 将当前活跃的 effect 添加到该属性的依赖集合中dep.add(activeEffect);
}/*** 触发更新,当属性值变化时执行所有相关的 effect* @param {object} target - 目标对象(被代理的对象)* @param {string|symbol} key - 发生变化的属性名*/
function trigger(target, key) {// 获取目标对象的依赖映射const depsMap = targetMap.get(target);// 如果没有依赖映射,直接返回(说明没有依赖需要触发)if (!depsMap) return;// 获取该属性对应的依赖集合const dep = depsMap.get(key);// 如果存在依赖集合,则遍历执行所有 effectdep && dep.forEach(effect => effect());
}/*** 创建响应式状态* @param {any} initialValue - 初始值* @param {string} [identifier] - 可选标识符,用于DOM绑定* @returns {Array} - 返回一个数组,包含状态值、更新函数和清理函数*/
export function useState(initialValue, identifier) {// 生成绑定键名,如果未提供标识符则自动生成(state_0, state_1...)const bindKey = identifier || `state_${uid++}`;// 创建响应式代理对象,只代理 value 属性const state = new Proxy({ value: initialValue }, {/*** 拦截属性读取操作* @param {object} target - 目标对象* @param {string|symbol} key - 被访问的属性名* @returns {any} - 返回属性值*/get(target, key) {// 只有访问 value 属性时才进行依赖收集if (key === 'value') track(target, key);// 使用 Reflect 获取原始值return Reflect.get(target, key);},/*** 拦截属性设置操作* @param {object} target - 目标对象* @param {string|symbol} key - 被设置的属性名* @param {any} value - 新值* @returns {boolean} - 返回是否设置成功*/set(target, key, value) {// 只有设置 value 属性时才触发更新if (key === 'value') {const oldValue = target.value;// 如果新旧值相同,则不触发更新(性能优化)if (oldValue === value) return true;// 设置新值target.value = value;// 触发该属性的所有依赖更新trigger(target, key);}return true;}});/*** 更新DOM的函数,将状态值同步到所有绑定的DOM元素*/const updateDOM = () => {// 获取所有绑定该状态的DOM元素const elements = document.querySelectorAll(`[data-bind="${bindKey}"]`);elements.forEach(el => {// 如果是input元素,更新其value(双向绑定)if (el.tagName.toLowerCase() === 'input') {// 只有值不同时才更新,避免不必要的DOM操作el.value !== state.value && (el.value = state.value);} else {// 其他元素更新其文本内容el.textContent = state.value;}});};// 初始化双向绑定(为input元素添加事件监听)document.querySelectorAll(`[data-bind="${bindKey}"]`).forEach(el => {if (el.tagName.toLowerCase() === 'input') {// input事件处理函数const handler = e => {// 当input值变化时,更新状态值state.value = e.target.value;};// 添加事件监听el.addEventListener('input', handler);// 在元素上存储清理函数,用于后续移除监听el._cleanup = () => el.removeEventListener('input', handler);}});// 初始渲染流程:// 1. 设置当前活跃的effect为updateDOMactiveEffect = updateDOM;// 2. 执行updateDOM(会触发getter,从而收集依赖)updateDOM();// 3. 重置activeEffectactiveEffect = null;// 返回数组,包含:return [// 1. 当前状态值state.value,// 2. 更新函数,支持直接传值或函数式更新(newValue) => {if (typeof newValue === 'function') {// 函数式更新:传入当前值,返回新值state.value = newValue(state.value);} else {// 直接设置新值state.value = newValue;}},// 3. 清理函数,用于移除所有事件监听() => {document.querySelectorAll(`[data-bind="${bindKey}"]`).forEach(el => {// 执行存储在元素上的清理函数el._cleanup?.();// 删除清理函数引用delete el._cleanup;});}];
}

7.3 测试的html文件 

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>原生JS响应式数据绑定示例</title><style>body {font-family: Arial, sans-serif;max-width: 600px;margin: 0 auto;padding: 20px;}.container {border: 1px solid #ddd;padding: 20px;border-radius: 8px;margin-top: 20px;}button {padding: 8px 16px;margin-right: 10px;cursor: pointer;}input {padding: 8px;width: 100%;box-sizing: border-box;margin-bottom: 10px;}.output {margin: 15px 0;padding: 10px;background-color: #f5f5f5;border-radius: 4px;}</style>
</head>
<body>
<h1>原生JS响应式数据绑定</h1><div class="container"><h2>双向绑定示例</h2><input type="text" data-bind="message" placeholder="在这里输入..."><div class="output"><p>当前内容: <span data-bind="message"></span></p></div><button id="changeBtn">更改消息</button><button id="resetBtn">重置</button>
</div><div class="container"><h2>计数器示例</h2><p>当前计数: <span data-bind="counter"></span></p><button id="incrementBtn">增加</button><button id="decrementBtn">减少</button>
</div><script type="module">// 导入 reactivity.js 的 useState 函数import { useState } from './reactivity.js';// 1. 双向绑定示例const [message, setMessage, cleanupMessage] = useState("初始消息", "message");// 按钮事件[更改消息]document.getElementById('changeBtn').addEventListener('click', () => {setMessage("这是新消息 " + new Date().toLocaleTimeString());});// 按钮事件[重置]document.getElementById('resetBtn').addEventListener('click', () => {setMessage("");});// 2. 计数器示例const [counter, setCounter] = useState(0, "counter");// 增加document.getElementById('incrementBtn').addEventListener('click', () => {setCounter(c => c + 1);});// 减少document.getElementById('decrementBtn').addEventListener('click', () => {setCounter(c => c - 1);});// 在页面卸载时清理(防止内存泄漏)window.addEventListener('beforeunload', () => {cleanupMessage();// 如果有其他 state 也需要清理...});
</script>
</body>
</html>

7.4 效果展示

用法 

7.5 useState参数说明:

const [value, setValue, cleanup] = useState(initialValue, identifier);

initialValue (必需)

  • 类型:任意(数字、字符串、对象等)

  • 作用:状态的初始值

  • 示例:

    useState(0)          // 数字
    useState("hello")    // 字符串
    useState({ a: 1 })   // 对象

identifier (可选)

  • 类型:字符串

  • 作用:唯一标识符,用于关联 DOM 元素的 data-bind 属性

  • 默认值:自动生成 state_0state_1 等(通过 uid++

  • 示例:

    useState("hello", "message")  // 手动指定标识符

7.6 useState返回值:

返回一个数组,包含三个元素:

const [value, setValue, cleanup] = useState(...);

value

当前状态值(响应式,变化会自动更新 DOM)

setValue

更新状态的函数,支持两种用法:

setValue("新值")               // 直接设置新值
setValue(prev => prev + 1)    // 基于旧值计算新值

cleanup (可选)

// 示例:在页面卸载时清理
window.addEventListener('beforeunload', cleanup);
  • 类型:函数

  • 作用:清理与该状态关联的所有 DOM 事件监听器(防止内存泄漏)

  • 调用时机:组件卸载或不再需要该状态时

7.7 useState使用示例:

1. 基本用法(自动生成标识符):

const [count, setCount] = useState(0);
// DOM 绑定:<span data-bind="state_0"></span>

2. 指定标识符(推荐):

const [text, setText] = useState("", "message");
// DOM 绑定:<input data-bind="message">

3. 带清理的用法:

const [user, setUser, cleanupUser] = useState(null, "userData");// 不再需要时清理
cleanupUser();

7.8 useState注意事项:

1.必须通过 data-bind 属性关联 DOM 元素,例如:

<div data-bind="message"></div>
<input data-bind="message">

2.双向绑定仅对 input 元素自动生效,其他元素需手动处理事件。

3.如果多个 useState 需要联动(如计算属性),需在 setValue 回调中处理:

const [firstName, setFirstName] = useState("", "firstName");
const [lastName, setLastName] = useState("", "lastName");
const [fullName, setFullName] = useState("", "fullName");// 当 firstName 或 lastName 变化时更新 fullName
setFirstName(name => {setFullName(`${name} ${lastName}`);return name;
});setLastName(name => {setFullName(`${firstName} ${name}`);return name;
});

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

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

相关文章

02.使用cline(VSCode插件)、continue(IDEA插件)、cherry-studio玩转MCP

文章目录 安装环境uv&#xff08;python&#xff09;为什么不用pip&#xff1f;安装 nvm&#xff08;nodejs&#xff09; cline插件window配置如下linux配置如下测试MCP&#xff1a;time现在几点了&#xff1f;伦敦现在几点了&#xff1f;当纽约是下午四点&#xff0c;那伦敦是…

CSS padding(填充)学习笔记

CSS 中的 padding&#xff08;填充&#xff09;是一个非常重要的属性&#xff0c;它用于定义元素边框与元素内容之间的空间&#xff0c;即上下左右的内边距。合理使用 padding 可以让页面布局更加美观、清晰。以下是对 CSS padding 的详细学习笔记。 一、padding 的作用 padd…

Spring 单元测试核心注解全解:@InjectMocks、@MockBean、@Mock、@Autowired 的区别与实战

在编写 Spring Boot 应用的单元测试过程中,@InjectMocks、@MockBean、@Mock 和 @Autowired 是最常用的几个注解,但它们经常被混淆或误用,导致测试失败或注入错误。 本文将从本质区别、使用场景、示例代码、对比表格等多个维度,全面解析这几者的使用方法与差异,助你写出结…

Themeleaf复用功能

Themeleaf复用功能 Thymeleaf 的复用功能能够有效减少代码冗余&#xff0c;提升开发效率&#xff0c;让代码更易于维护。以下为你详细介绍几种常见的复用功能&#xff1a; 1. 片段复用&#xff08;Fragments&#xff09; 定义片段 借助 th:fragment 指令&#xff0c;可将页…

前端面试题(八):简述Vue2的响应式原理

Vue 2 的响应式原理主要基于 数据劫持 和 发布-订阅模式&#xff0c;通过 Object.defineProperty 对对象的属性进行拦截&#xff0c;实现数据的监控与视图更新。具体原理如下&#xff1a; 1. 数据劫持&#xff1a;Object.defineProperty Vue 2 在初始化过程中&#xff0c;通过…

深度学习中的数值稳定性处理详解:以SimCLR损失为例

文章目录 1. 问题背景SimCLR的原始公式 2. 数值溢出问题为什么会出现数值溢出&#xff1f;浮点数的表示范围 3. 数值稳定性处理方法核心思想数学推导 4. 代码实现分解代码与公式的对应关系 5. 具体数值示例示例&#xff1a;相似度矩阵方法1&#xff1a;直接计算exp(x)方法2&…

SQL(9):创建数据库,表,简单

1、创建数据库&#xff0c;一句SQL语句搞定 CREATE DATDBASE 数据库名 CREATE DATABASE my_db;2、创建表 CREATE TABLE 表名(字段名 类型) CREATE TABLE Persons ( PersonID int, LastName varchar(255), FirstName varchar(255), Address varchar(255), City varchar(255)…

QT Sqlite数据库-教程002 查询数据-下

【1】数据库查询的优化&#xff1a;prepare prepare语句是一种在执行之前将SQL语句编译为字节码的机制&#xff0c;可以提高执行效率并防止SQL注入攻击。 【2】使用prepare查询一张表 QString myTable "myTable" ; QString cmd QString("SELECT * FROM %1…

cline 提示词工程指南-架构篇

cline 提示词工程指南-架构篇 本篇是 cline 提示词工程指南的学习和扩展&#xff0c;可以参阅&#xff1a; https://docs.cline.bot/improving-your-prompting-skills/prompting 前言 cline 是 vscode 的插件&#xff0c;用来在 vscode 里实现 ai 编程。 它使得你可以接入…

算法---子序列[动态规划解决](最长递增子序列)

最长递增子序列 子序列包含子数组&#xff01; 说白了&#xff0c;要用到双层循环&#xff01; 用双层循环中的dp[i]和dp[j]把所有子序列情况考虑到位 class Solution { public:int lengthOfLIS(vector<int>& nums) {vector<int> dp(nums.size(),1);for(int i …

kubectl命令补全以及oc命令补全

kubectl命令补全 1.安装bash-completion 如果你用的是Bash(默认情况下是)&#xff0c;先安装补全功能支持包 sudo apt update sudo apt install bash-completion -y2.为kubectl 启用补全功能 会话中临时&#xff1a; source <(kubectl completion bash)持久化配置&#x…

48、Spring Boot 详细讲义(五)

3、集成MyBatis 3.1 MyBatis 概述 3.1.1 核心功能和优势 MyBatis 是一个 Java 持久层框架,它通过 XML 或注解配置 SQL 语句,将 Java 方法与 SQL 语句映射起来,消除了大量的 JDBC 代码,简化了数据库操作。MyBatis 的核心功能和优势包括: ORM(对象关系映射):通过 XML …

BERT - Bert模型框架复现

本节将实现一个基于Transformer架构的BERT模型。 1. MultiHeadAttention 类 这个类实现了多头自注意力机制&#xff08;Multi-Head Self-Attention&#xff09;&#xff0c;是Transformer架构的核心部分。 在前几篇文章中均有讲解&#xff0c;直接上代码 class MultiHeadAtt…

解决 Spring Boot 启动报错:数据源配置引发的启动失败

启动项目时&#xff0c;控制台输出了如下错误信息&#xff1a; Error starting ApplicationContext. To display the condition evaluation report re-run your application with debug enabled. 2025-04-14 21:13:33.005 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporte…

履带小车+六轴机械臂(2)

本次介绍原理图部分 开发板部分&#xff0c;电源供电部分&#xff0c;六路舵机&#xff0c;PS2手柄接收器&#xff0c;HC-05蓝牙模块&#xff0c;蜂鸣器&#xff0c;串口&#xff0c;TB6612电机驱动模块&#xff0c;LDO线性稳压电路&#xff0c;按键部分 1、开发板部分 需要注…

【开发记录】服务外包大赛记录

参加服务外包大赛的A07赛道中&#xff0c;最近因为频繁的DEBUG&#xff0c;心态爆炸 记录错误 以防止再次出现错误浪费时间。。。 2025.4.13 项目在上传图片之后 会自动刷新 没有等待后端返回 Network中的fetch /upload显示canceled. 然而这是使用了VS的live Server插件才这样&…

基于FreeRTOS和LVGL的多功能低功耗智能手表(硬件篇)

目录 一、简介 二、板子构成 三、核心板 3.1 MCU最小系统板电路 3.2 电源电路 3.3 LCD电路 3.4 EEPROM电路 3.5 硬件看门狗电路 四、背板 4.1 传感器电路 4.2 充电盘 4.3 蓝牙模块电路 五、总结 一、简介 本篇开始介绍这个项目的硬件部分&#xff0c;从最小电路设…

为 Kubernetes 提供智能的 LLM 推理路由:Gateway API Inference Extension 深度解析

现代生成式 AI 和大语言模型&#xff08;LLM&#xff09;服务给 Kubernetes 带来了独特的流量路由挑战。与典型的短时、无状态 Web 请求不同&#xff0c;LLM 推理会话通常是长时运行、资源密集且部分有状态的。例如&#xff0c;一个基于 GPU 的模型服务器可能同时维护多个活跃的…

MacOs下解决远程终端内容复制并到本地粘贴板

常常需要在服务器上捣鼓东西&#xff0c;同时需要将内容复制到本地的需求。 1-内容是在远程终端用vim打开&#xff0c;如何用vim的类似指令达到快速复制到本地呢&#xff1f; 假设待复制的内容&#xff1a; #include <iostream> #include <cstring> using names…

STM32 vs ESP32:如何选择最适合你的单片机?

引言 在嵌入式开发中&#xff0c;STM32 和 ESP32 是两种最热门的微控制器方案。但许多开发者面对项目选型时仍会感到困惑&#xff1a;到底是选择功能强大的 STM32&#xff0c;还是集成无线的 ESP32&#xff1f; 本文将通过 硬件资源、开发场景、成本分析 等多维度对比&#xf…