MUI Select 自定义菜单项无法正常工作

MUI Select custom MenuItem not working properly

我有一个关于 MUI MenuItemSelect 结合并在单独的组件中呈现它的问题。

这是codesandbox

基本上,我有这样的东西:

import { Select } from "@material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";

export default function App() {
  const userIds = [1, 2, 3];
  return (
    <Select
      id="user"
      name="User"
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        alert(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <CustomMenuItem key={userId} userId={userId} />
      ))}
    </Select>
  );
}

这是自定义项:

import { MenuItem, Typography } from "@material-ui/core";
import React from "react";

interface CustomMenuItemProps {
  userId: number;
}

const CustomMenuItem = React.forwardRef<HTMLLIElement, CustomMenuItemProps>(
  (props: CustomMenuItemProps, ref) => {
    const { userId, ...rest } = props;
    return (
      <MenuItem value={userId} {...rest} ref={ref}>
        <Typography>{userId}</Typography>
      </MenuItem>
    );
  }
);
export default CustomMenuItem;

起初,我在没有任何 refs 的情况下完成了此操作,但这在控制台中给了我一个错误 (Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?),所以在谷歌搜索了一段时间后,我发现我必须通过这个 ref .我还传递了 ...rest 的道具,据我了解 MenuItem 需要它们。

预期行为:当我单击 MenuItem 时,它会在 Select 组件中被选中。

实际行为:没有任何反应。

问题是,我制作了 CustomMenuItem 以使其可重复使用。但在那之前,我有一个简单的函数,例如:renderItem,我在 Select.renderValueuserIds.map 中都使用了它,它与 CustomMenuItem 具有相同的代码 - 它返回相同的代码JSX 树。它当时有效,但由于某种原因现在无效。所以如果我愿意:

    <Select
      id="user"
      name="User"
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        alert(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <MenuItem key={userId} value={userId}>
          <Typography>{userId}</Typography>
        </MenuItem>
      ))}
    </Select>

它很简单:(

我是不是漏掉了什么?

Select 的一些实现细节妨碍了尝试以这种方式自定义 MenuItem

Select uses the value prop 的直系子代。在你的例子中 Select 的直接子元素是 CustomMenuItem 元素,它们只有一个 userId 道具——而不是 value 道具;因此 Select 在您单击其中一个自定义菜单项时发现 undefined 作为新值。

您可以通过将 userId 道具复制为 value 道具来解决这个问题:

import { Select } from "@material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";

export default function App() {
  const userIds = [1, 2, 3];
  const [value, setValue] = React.useState(1);
  console.log("value", value);
  return (
    <Select
      id="user"
      name="User"
      value={value}
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        setValue(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <CustomMenuItem key={userId} value={userId} userId={userId} />
      ))}
    </Select>
  );
}

如果您查看控制台日志,这会成功更改 Select 的值。新值 由于一个单独的问题而成功显示,我将在稍后解释。

你可能会想“那么我可以只使用 value 道具而不是 userId 道具,而不是同时使用两者”,但 value 道具实际上不会到达你的自定义组件。 Select 使用 React.cloneElementchange the value prop to undefined 而不是将其放在 data-value 中以避免在最终的 html 中指定 value 道具(这不会' t 是呈现的 html 元素的有效属性。

在我上面的沙盒中,您会注意到当您 select 一个值时,新值没有成功显示为 selected 值。这是因为 Select uses the children prop of the selected child as the display value unless you specify the renderValue propCustomMenuItem 元素的 children 属性未定义。

您可以通过在 Select 上使用 renderValue 属性或再次指定 userId 来解决此问题:

import { Select } from "@material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";

export default function App() {
  const userIds = [1, 2, 3];
  const [value, setValue] = React.useState(1);
  console.log("value", value);
  return (
    <Select
      id="user"
      name="User"
      value={value}
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        setValue(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <CustomMenuItem key={userId} value={userId} userId={userId}>
          {userId}
        </CustomMenuItem>
      ))}
    </Select>
  );
}

这有效,但也删除了自定义菜单项组件试图提供的所有值。我认为实现这一点的最简单方法(同时仍然可以很好地使用 Material-UI Select 设计)是将可重用代码放在用于呈现菜单项的函数中,而不是制作自定义菜单项组件:

import { Select } from "@material-ui/core";
import React from "react";
import { MenuItem, Typography } from "@material-ui/core";

const renderMenuItem = (value: number) => {
  return (
    <MenuItem key={value} value={value}>
      <Typography>{value}</Typography>
    </MenuItem>
  );
};

export default function App() {
  const userIds = [1, 2, 3];
  const [value, setValue] = React.useState(1);
  console.log("value", value);
  return (
    <Select
      id="user"
      name="User"
      value={value}
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        setValue(event.target.value as number);
      }}
    >
      {userIds.map(renderMenuItem)}
    </Select>
  );
}