【问题标题】:Extracting React Array of Elements Into a Function Component将 React 元素数组提取到函数组件中
【发布时间】:2020-04-23 07:32:15
【问题描述】:

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

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;

...一次在select 类型的Textfield 内,另一次在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)

【问题讨论】:

  • 请创建一个工作示例(您可以使用codesandbox.io)。
  • 您向我们展示的代码没有使用任何引用。当你渲染一个&lt;MenuItemList /&gt; 时,你是在尝试传入一个 ref 吗?
  • 一点也不,我只是按原样渲染组件。可能是 TextField 自动执行此操作吗?
  • @NicholasTower 我认为上面的错误堆栈显示了谁在尝试传递参考?

标签: reactjs material-ui react-forwardref


【解决方案1】:

stefano.orlando 的解释似乎是正确的,直到他们提出解决方案。将 div 包裹在其他组件周围不会导致 ref 被转发。为此,您需要使用 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;

【讨论】:

  • 非常感谢,我马上试试!知道如何在上面输入propsref 吗?
【解决方案2】:

看来问题不在这个组件上。你能粘贴你使用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 组件的“内容锚”。 “内容锚点”是菜单内的 DOM 元素,Popover 尝试与锚元素(菜单外的元素,作为定位菜单的参考点)对齐。

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

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

所以你应该把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,但即使使用 div,我也会得到完全相同的错误和错误堆栈:-(
猜你喜欢
  • 2020-04-14
  • 2013-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-24
  • 2013-05-21
  • 2021-06-16
  • 1970-01-01
相关资源
最近更新 更多