将 React 元素数组提取到函数组件中

Extracting React Array of Elements Into a Function Component

我正在尝试使用我编写的这个组件:

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

// See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20356#issuecomment-435708501
// for an explanation on why the component return type was cast to `any` below.
const MenuItemList: any = () => {
  return Object.values(ITEMS).map(
    (item) =>
      !item.protected && (
        <MenuItem key={item.id} value={item.id}>
          <ListItemText
            primary={item.name}
            secondary={item.description}
          />
        </MenuItem>
      )
  );
};

export default MenuItemList;

...一次在类型 selectTextfield 中,另一次在 Menu 组件中。但是,在浏览器中访问时出现以下错误:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

知道如何解决这个问题吗?

更新:

我是这样调用这个组件的:

<Menu
  id={id}
  open={open}
  anchorEl={anchorEl}
  getContentAnchorEl={null}
  keepMounted={false}
  onClose={handleClose}
  elevation={2}
  PaperProps={{
      square: true,
  }}
  anchorOrigin={{
      vertical: 'bottom',
      horizontal: 'right',
  }}
  transformOrigin={{
      vertical: 'top',
      horizontal: 'right',
  }}
>
  < MenuItemList />
</Menu>

<Field
  name="items"
  label="Select Item"
  padding={2}
  component={TextField}
  select
  fullWidth
  SelectProps={{
    MenuProps: {
      elevation: 2,
      getContentAnchorEl: null,
      anchorOrigin: {
        vertical: 'bottom',
        horizontal: 'left',
      },
      transformOrigin: {
        vertical: 'top',
        horizontal: 'left',
      },
    },
    IconComponent: ExpandMoreIcon,
  }}
  variant="filled"
  InputProps={{
    disableUnderline: true,
  }}
>
  <MenuItemList />
</Field>

更新 2

这是错误堆栈:

Check the render method of `ForwardRef(Menu)`.
    in MenuItemList (at ShowItemsDialog.tsx:105)
    in ul (created by ForwardRef(List))
    in ForwardRef(List) (created by WithStyles(ForwardRef(List)))
    in WithStyles(ForwardRef(List)) (created by ForwardRef(MenuList))
    in ForwardRef(MenuList) (created by ForwardRef(Menu))
    in div (created by ForwardRef(Paper))
    in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
    in WithStyles(ForwardRef(Paper)) (created by Transition)
    in Transition (created by ForwardRef(Grow))
    in ForwardRef(Grow) (created by TrapFocus)
    in TrapFocus (created by ForwardRef(Modal))
    in div (created by ForwardRef(Modal))
    in ForwardRef(Portal) (created by ForwardRef(Modal))
    in ForwardRef(Modal) (created by ForwardRef(Popover))
    in ForwardRef(Popover) (created by WithStyles(ForwardRef(Popover)))
    in WithStyles(ForwardRef(Popover)) (created by ForwardRef(Menu))
    in ForwardRef(Menu) (created by WithStyles(ForwardRef(Menu)))
    in WithStyles(ForwardRef(Menu)) (created by ForwardRef(SelectInput))
    in ForwardRef(SelectInput) (created by ForwardRef(InputBase))
    in div (created by ForwardRef(InputBase))
    in ForwardRef(InputBase) (created by WithStyles(ForwardRef(InputBase)))
    in WithStyles(ForwardRef(InputBase)) (created by ForwardRef(FilledInput))
    in ForwardRef(FilledInput) (created by WithStyles(ForwardRef(FilledInput)))
    in WithStyles(ForwardRef(FilledInput)) (created by ForwardRef(Select))
    in ForwardRef(Select) (created by WithStyles(ForwardRef(Select)))
    in WithStyles(ForwardRef(Select)) (created by ForwardRef(TextField))
    in div (created by ForwardRef(FormControl))
    in ForwardRef(FormControl) (created by WithStyles(ForwardRef(FormControl)))
    in WithStyles(ForwardRef(FormControl)) (created by ForwardRef(TextField))
    in ForwardRef(TextField) (created by WithStyles(ForwardRef(TextField)))
    in WithStyles(ForwardRef(TextField)) (created by FormikMaterialUITextField)
    in FormikMaterialUITextField (created by Field)
    in Field (at ShowItemsDialog.tsx:77)

看来问题不在这个组件上。您可以粘贴您使用 MenuItemList 的组件的代码吗?似乎是 Material-ui

使用的 ref react 的问题

我也推荐使用filter [doc]来过滤保护项。

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

const MenuItemList: any = () => {
  return Object.values(ITEMS).filter(item => !item.protected).map(
    (item) =>
        <MenuItem key={item.id} value={item.id}>
          <ListItemText
            primary={item.name}
            secondary={item.description}
          />
        </MenuItem>
  );
};

export default MenuItemList;

编辑

Menu 使用 Menu 的第一个子项作为 Menu 内部使用的 Popover 组件的 "content anchor"。 "content anchor" 是菜单中的 DOM 元素,Popover 试图与锚元素(菜单外部的元素,是定位菜单的参考点)对齐。

为了利用第一个子项作为内容锚点,Menu 向其添加了一个 ref(使用 cloneElement)。为了不得到您收到的错误(并且为了定位正常工作),您的功能组件需要将 ref 转发到它呈现的组件之一(通常是最外层的组件 - 在您的情况下为 div) .

当您使用 div 作为 Menu 的直接子项时,您不会收到错误,因为 div 可以成功接收引用。

所以你应该把MenuItemList的代码改成:

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

const MenuItemList: any = () => {
  return (
    <div>
      {Object.values(ITEMS)
        .filter(item => !item.protected)
        .map(item => (
          <MenuItem key={item.id} value={item.id}>
            <ListItemText primary={item.name} secondary={item.description} />
          </MenuItem>
        ))}
    </div>
  );
};

export default MenuItemList;

stefano.orlando的解释似乎是正确的,直到他们提出解决方案。将 div 包裹在其他组件周围不会导致引用被转发。为此,您需要使用 React.forwardRef。下面的代码将使得如果将 ref 传递到 MenuItemList,则该 ref 将被重定向到 MenuItemList 中的第一个 MenuItem 而不是:

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

const MenuItemList: any = React.ForwardRef((props, ref) => {
  return (
    Object.values(ITEMS)
      .filter(item => !item.protected)
      .map((item, index) => (
        <MenuItem 
          ref={index === 0 ? ref : undefined}
          key={item.id} 
          value={item.id}
        >
          <ListItemText primary={item.name} secondary={item.description} />
        </MenuItem>
      ))
  );
});

export default MenuItemList;