在useReducer中使用useRef遇到Strict Mode下ID重复递增问题
React useReducer ID递增异常与Ref使用问题解决方案
一、reducer中更新useRef是否合法?
不合法。React要求reducer必须是纯函数:它不能修改外部状态、不能执行副作用操作(比如更新ref、发起网络请求等),且相同输入必须返回相同输出。
你当前在reducer里修改localMessageId.current属于副作用操作,这不仅违反了纯函数原则,还会在Strict Mode下触发问题——因为Strict Mode会刻意重复执行reducer(用于检测副作用),导致ID被意外递增两次。
二、解决Strict Mode下ID跳变+获取新ID的方案
方案1:将ID生成逻辑移出reducer(推荐)
把ID生成放在dispatch动作之前,封装成独立的添加函数,同时直接返回新生成的ID。这样reducer只负责纯数据处理,完全规避副作用问题。
修改后的代码:
import { useRef, useReducer } from "react"; const useMessages = () => { const localMessageId = useRef(1); const [messages, dispatchMessages] = useReducer( (messages, action) => { switch (action.type) { case "add": // 直接使用已带ID的消息,reducer仅做纯数据拼接 return [...messages, action.message]; // 其他动作保留 default: return messages; } }, [] ); // 封装添加消息的函数,生成ID并返回 const addMessage = (message) => { const newId = localMessageId.current++; const newMessage = { ...message, id: newId }; dispatchMessages({ type: "add", message: newMessage }); return newId; // 直接返回新生成的ID }; return [messages, addMessage]; }; export default useMessages;
调用示例:
const [messages, addMessage] = useMessages(); // 添加消息时直接获取新ID const newMsgId = addMessage({ content: "Hello World" }); console.log("新消息ID:", newMsgId);
方案2:用reducer统一管理ID计数器
如果希望所有状态都由reducer管控,可以把ID计数器纳入reducer的state中,让reducer纯函数式地更新计数器和消息列表。Strict Mode下重复执行reducer时,因为是纯函数,两次执行结果一致,React会自动忽略重复更新,不会导致ID跳变。
修改后的代码:
import { useReducer } from "react"; // 初始化状态包含消息列表和下一个可用ID const initialState = { messages: [], nextId: 1 }; const messageReducer = (state, action) => { switch (action.type) { case "add": const targetId = action.message.id ?? state.nextId; return { messages: [...state.messages, { ...action.message, id: targetId }], nextId: targetId + 1 }; // 其他动作保留 default: return state; } }; const useMessages = () => { const [state, dispatchMessages] = useReducer(messageReducer, initialState); const addMessage = (message) => { const newId = message.id ?? state.nextId; dispatchMessages({ type: "add", message }); return newId; }; return [state.messages, addMessage]; }; export default useMessages;
三、关键总结
- 永远保持reducer的纯函数特性,不要在其中修改外部状态(包括ref)、执行副作用。
- 获取新ID的最优方式是在触发dispatch前生成并返回,避免从更新后的状态中异步提取。
- Strict Mode下的重复执行是React的检测机制,只要代码遵循纯函数原则,就不会出现异常行为。
内容的提问来源于stack exchange,提问作者Robo Robok
相关产品推荐
相关产品推荐

