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

React+TypeScript封装MUI MenuItem自定义组件各类报错的解决方法

MUI MenuItem 自定义封装的正确实现方案

报错根因拆解

  • 类型不匹配导致TS编译错误:MUI MenuItem 默认渲染根节点为<li>,对应可接收的ref类型是HTMLLIElement;只有开启button属性模式时,根节点才会渲染为<button>,对应HTMLButtonElement。原代码把ref类型声明为HTMLButtonElement,和MenuItem实际承接的ref类型不兼容,直接触发TS的赋值检查报错。
  • 组件封装遗漏核心步骤:仅定义了ForwardRefRenderFunction类型的渲染函数,没有通过React.forwardRef将渲染函数包装为可承接外部ref的正式组件,导出的本质还是普通函数组件。而MUI Select 在渲染菜单项时,会通过cloneElement给每个直接子菜单项绑定内部管理的ref(用于键盘导航、滚动定位等逻辑),普通函数组件无法承接这类ref,自然触发"Function components cannot be given refs"的警告。
  • 强转any的运行时副作用:把泛型改成any跳过类型检查后,由于组件本身没有ref透传能力,Select内部往组件上挂载ref时,会尝试在不可扩展的函数对象上添加current属性,最终触发Cannot add property current, object is not extensible的运行时崩溃。

正确的 MenuItem 封装实现

完整代码需要对齐类型匹配、完成forwardRef包装两个核心要求:

import { forwardRef } from 'react';
import MenuItem, { MenuItemProps } from '@mui/material/MenuItem';

// 对齐MenuItem的props泛型,默认渲染li元素
type DMSMenuItemProps = MenuItemProps<"li", { button?: true }>;

// 泛型参数第一个匹配ref指向的DOM类型HTMLLIElement,第二个匹配组件Props类型
const DMSMenuItem = forwardRef<HTMLLIElement, DMSMenuItemProps>((props, ref) => {
  return <MenuItem ref={ref} {...props} />;
});

// 补充组件名,方便DevTools调试
DMSMenuItem.displayName = 'DMSMenuItem';

export default DMSMenuItem;

如果需要支持button模式下的按钮类型MenuItem,可以将封装调整为泛型组件适配不同根DOM类型,不需要强转any:

// 支持button模式的泛型封装版本
const DMSMenuItem = forwardRef<HTMLElement, MenuItemProps & { button?: true }>((props, ref) => {
  return <MenuItem ref={ref} {...props} />;
});

配套的 Select 组件封装优化

原DMSSelect 是普通函数组件,如果后续需要接收外部传入的ref(比如手动触发Select焦点、获取内部DOM元素),也需要同步做forwardRef封装,避免同类报错:

import { forwardRef } from 'react';
import { Select, SelectProps } from '@mui/material/Select';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import FormHelperText from '@mui/material/FormHelperText';

interface IDMSSelectProps extends SelectProps {
  hint?: string;
  label: string;
}

const DMSSelect = forwardRef<HTMLDivElement, IDMSSelectProps>((props, ref) => {
  // 解构出自定义属性,避免透传到原生DOM触发非标准属性警告
  const { error, label, children, hint, ...restSelectProps } = props;
  return (
    <FormControl error={error} fullWidth>
      <InputLabel>{label}</InputLabel>
      <Select ref={ref} fullWidth {...restSelectProps}>
        {children}
      </Select>
      <FormHelperText>{hint}</FormHelperText>
    </FormControl>
  );
});

DMSSelect.displayName = 'DMSSelect';

export default DMSSelect;

封装注意事项

  • 不要直接导出ForwardRefRenderFunction类型的原始函数:该类型仅用于标注传入forwardRef的渲染函数的参数结构,本身不具备承接ref的能力,必须经过React.forwardRef包装后导出才能在作为Select子组件时正常工作。
  • 避免用any跳过类型检查:类型不匹配时优先对齐MUI组件自身的类型定义,不要通过强转any规避检查,否则会隐藏运行时的ref透传问题。
  • 透传属性时要解构出自定义扩展的props,不要把非MUI组件要求的属性(比如hint)直接spread给原生DOM,否则会触发React的非标准DOM属性警告。

内容的提问来源于stack exchange,提问作者Pierre-luc Des.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 18:36:05