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

为何React的forwardRef与Material UI的styled无法兼容?

forwardRef 与 styled 配合使用时 Hook 报错的原因及解决方案

问题描述

当同时使用 forwardRef 和 Material-UI 的 styled 时,出现如下错误:

React Hook……不能在回调函数内部调用。React Hooks必须在React函数组件或自定义React Hook函数中调用。

对应的三个代码示例:

正常运行的代码1(仅使用styled)

import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

const FooComponent = styled(({ className }) => {
  const { t } = useTranslation();
  return <div className={className}>{t('This is fine')}</div>;
})(() => ({
  // CSS styles...
}));

正常运行的代码2(仅使用forwardRef)

import { forwardRef } from 'react';
import { useTranslation } from '../lib/i18n';

const FooComponent = forwardRef((props, ref) => {
  const { t } = useTranslation();
  return <div ref={ref}>{t('This is fine, too!')}</div>;
});

报错的代码3(forwardRef + styled 混用)

import { forwardRef } from 'react';
import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

const FooComponent = forwardRef( styled(({ className }, ref) => {
  const { t } = useTranslation();    // ERROR here!
  return <div ref={ref} className={className}>{t('This is NOT fine')}</div>; 
})(() => ({
  // CSS styles...
})));

报错原因

第三个案例报错的核心是函数执行上下文违反了React Hook的调用规则:

  • styled() 接收的回调并非标准的React函数组件,而是被styled内部封装的渲染函数。
  • 当你把 styled() 的返回值直接传给 forwardRef 时,这个渲染函数会被forwardRef作为回调调用,此时 useTranslation 是在非顶层的回调函数内部执行的,违反了"Hooks必须在React函数组件或自定义Hook的顶层调用"的规则。
  • 对比前两个案例:
    • 案例1中,styled内部会自动把传入的回调包装成合法的React组件,Hook在顶层调用符合规则;
    • 案例2中,forwardRef的参数是标准的React组件函数,Hook在顶层调用也符合规则。

解决方案(不止自定义ref一种)

方案1:先创建带forwardRef的组件,再用styled包裹

先定义一个使用forwardRef的基础组件,再将其传入styled,确保Hook在标准React组件的顶层执行:

import { forwardRef } from 'react';
import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

// 先创建带forwardRef的基础组件
const BaseFoo = forwardRef(({ className }, ref) => {
  const { t } = useTranslation();
  return <div ref={ref} className={className}>{t('This works!')}</div>;
});

// 再用styled包裹
const FooComponent = styled(BaseFoo)(() => ({
  // CSS styles...
}));

方案2:使用自定义ref属性(如innerRef)

如果不想拆分组件,可以给styled组件传递自定义的ref属性,避免直接在styled的回调里使用forwardRef:

import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

const FooComponent = styled(({ className, innerRef }) => {
  const { t } = useTranslation();
  return <div ref={innerRef} className={className}>{t('This works too!')}</div>;
})(() => ({
  // CSS styles...
}));

// 使用时:
<FooComponent innerRef={yourRef} />

方案3:将Hook逻辑抽离到外层自定义Hook

如果Hook逻辑复杂,可把useTranslation的调用抽离到自定义Hook中,本质还是保证Hook在标准组件的顶层执行:

import { forwardRef } from 'react';
import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

// 自定义Hook(示例)
const useFooTranslation = () => {
  const { t } = useTranslation();
  return { text: t('This works as well!') };
};

const BaseFoo = forwardRef(({ className }, ref) => {
  const { text } = useFooTranslation();
  return <div ref={ref} className={className}>{text}</div>;
});

const FooComponent = styled(BaseFoo)(() => ({
  // CSS styles...
}));

内容的提问来源于stack exchange,提问作者Yanick Rochon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 04:05:19