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

React Rendered fewer hooks than expected报错排查

问题根因

你的判断完全正确,报错的核心诱因就是直接以函数调用形式执行组件,而非通过JSX语法渲染组件。
React的Hook顺序校验是和组件节点绑定的:

  • 用<Component />的JSX形式渲染时,React会将其识别为独立组件节点,组件内部的Hook调用会被归到该节点独立的Hook链表下,只要节点挂载/卸载顺序稳定就不会触发顺序类报错。
  • 直接写Component()调用函数时,React不会将其识别为独立组件,组件内部的所有Hook调用会被合并到当前父组件(也就是这里的PrepareText)的Hook链表中。

你的场景里,reactStringReplace匹配到的占位符数量会随text内容变化,条件分支也会选择渲染不同的Pill组件,只要两次渲染间调用的Pill函数数量不一致,PrepareText的Hook调用总数就会发生变化,直接触发「Rendered fewer hooks than expected」报错。
另外你传入的EntityPill/PlayerPill是每次渲染都重新创建的内联箭头函数,内部直接执行Cell组件,等于把Cell组件内的Hook(比如你引入的useSteamUser)全部插入到PrepareText的Hook调用队列里,Hook顺序完全无法稳定。

修复方案

核心原则:所有React组件必须通过JSX语法渲染,禁止直接以普通函数形式调用组件。

  1. 首先调整PrepareText组件的类型定义和渲染逻辑,将接收的Pill参数改为组件类型而非普通函数,渲染时用JSX写法:
import reactStringReplace from 'react-string-replace';
import type { ComponentType } from 'react';
import { Entity, Player } from './LogItem';

// 定义Pill组件统一接收的Props类型,和你现有CellProps保持一致即可
type PillBaseProps = {
  value: string | null;
  restricted?: boolean;
};

export default function PrepareText({
  subjects,
  text,
  restricted,
  EntityPill,
  PlayerPill,
}: {
  subjects: { player: Player; entity: Entity };
  text: string;
  restricted?: boolean;
  EntityPill: ComponentType<PillBaseProps>;
  PlayerPill: ComponentType<PillBaseProps>;
}) {
  return (
    <>
      {reactStringReplace(text, /(\${\w+})/g, (match, i) => {
        const key = match
          .replace('${', '')
          .replace('}', '') as keyof typeof subjects;
        const CurrentPill = key === 'player' ? PlayerPill : EntityPill;

        return (
          <span key={i}>
            {/* 用JSX语法渲染组件,Hook会被绑定到独立节点 */}
            <CurrentPill value={subjects[key]} restricted={restricted} />
          </span>
        );
      })}
    </>
  );
}
  1. 调整PrepareText的调用代码,直接传入配置中存储的组件引用,不要包内联箭头函数做函数调用:
<PrepareText
  subjects={{
    player: data.player,
    entity: data.entity,
  }}
  text={EVENTS[event].text}
  restricted={restricted}
  EntityPill={EVENTS[event].EntityCell}
  PlayerPill={EVENTS[event].PlayerCell}
/>
额外注意事项
  • 配置对象中存储的Icon是已经创建完成的JSX元素(ReactNode),可以直接插值渲染,不属于组件函数调用,不会触发Hook问题。
  • 哪怕当前Pill组件内部暂时没有使用Hook,也不要用直接调用函数的写法,后续迭代中一旦给Pill组件加了Hook调用,就会再次触发同类报错。

内容的提问来源于stack exchange,提问作者Eon Sberk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 08:33:21