探索原生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.pswp.cn/diannao/79026.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/79026.shtml
英文地址,請注明出處:http://en.pswp.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…