为何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
相关产品推荐
相关产品推荐

