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.單例模式和雙例模式的區別??★ 了解
-
單例模式:
-
全局共享一個狀態樹
-
直接導出?
useState
?和?batch
-
適合中小型應用
-
-
多例模式:
-
通過?
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 核心機制說明:
-
依賴收集 (track):
-
當讀取?
state.value
?時,如果存在?activeEffect
,會將這個 effect 收集起來 -
存儲結構:
WeakMap<target, Map<key, Set<effect>>>
-
-
觸發更新 (trigger):
-
當設置?
state.value
?時,會查找并執行所有收集到的 effect
-
-
響應式循環:
-
初始化時執行?
updateDOM
?會觸發?getter
?收集依賴 -
當值變化時觸發?
setter
,執行所有依賴的 effect(包括?updateDOM
)
-
-
雙向綁定:
-
自動為?
input
?元素添加?input
?事件監聽 -
輸入變化時更新狀態,狀態變化時更新輸入框值
-
-
清理機制:
-
每個綁定元素存儲了自己的清理函數
-
可以在不需要時移除所有事件監聽,防止內存泄漏
-
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_0
、state_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;
});