You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 10:44:57