【问题标题】:How to support different types of icons being passed to a react component?如何支持将不同类型的图标传递给反应组件?
【发布时间】:2021-09-18 00:22:29
【问题描述】:

我正在开发几个组件,我需要在我的组件中显示图标。

假设我的组件的使用代码如下所示:

<SomeComponent
    ...otherProps
    icon=X
/>

我意识到开发人员倾向于以两种方式使用图标:

  1. icon='done'
  2. icon={Done}
  3. icon={&lt;Done /&gt;}
  4. icon={doneSvg}

为了更加通用,我创建了这个CustomIcon 组件:

import Icon from '@material-ui/core/Icon';

const CustomIcon = ({ icon }) => {
    console.log(icon);
    switch (typeof icon) {
        case 'object':
            if (icon.props) {
                return icon;
            }
            return <>{icon}</>;
        case 'function':
            return icon()
        case 'string':
            if (icon.indexOf('svg') > -1) {
                return icon;
            }
            return <Icon>{icon}</Icon>;
        default:
            return <span>Iconless</span>;
    }
}

export default CustomIcon;

但是当我通过 icon={DoneIcon} 并带有以下消息时它失败了:

Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, compare}). If you meant to render a collection of children, use an array instead.

我该如何解决这个问题?

更新

这是CodeSandbox

【问题讨论】:

  • 什么是 DoneIcon?
  • @iunfixit,在这种情况下,DoneIconimport DoneIcon from '@material-ui/icons/Done';

标签: reactjs


【解决方案1】:

你没有考虑一个记忆组件函数(它有{$$typeof, type, compare}

const CustomIcon = ({ icon }) => {
    console.log(icon);
    
    switch (typeof icon) {
        case 'object':
            const iconType = typeof icon?.type;
            if (
             iconType === 'function'
               || (iconType === 'object' && typeof icon.type?.render === 
               'function'))    {
              const Icon = icon
              return <Icon/>;
            }
            if (icon.props) {
                return icon;
            }
            return <>{icon}</>;
        case 'function':
            return icon()
        case 'string':
            if (icon.indexOf('svg') > -1) {
                return icon;
            }
            return <Icon>{icon}</Icon>;
        default:
            return <span>Iconless</span>;
    }
}

也适用于可以用长度检查的数组

您也可以像其他答案一样使用解构别名

说明

当你记忆一个组件时,你会得到一个对象,如下所示

{type: Object, compare: null}

对于函数组件,type 是一个函数。您可以通过以下方式确认

const MyIcon = React.memo(() => {
  return <DeleteOutlinedIcon/>
})

console.log(MyIcon)

上面的代码会检查这种情况,并让 JSX 处理它

【讨论】:

  • 我试过了。没有解决问题。我正在尝试为它创建一个代码沙箱。
  • 我相信这是一个类组件,更新了答案
  • 你的新代码做到了。太感谢了。你能解释一下它背后的理论吗?我的意思是,如果没有理论,那就太令人困惑了。
  • 我还提供了一个代码沙箱。请查看更新后的问题。
  • 更新了让我知道它是否解释得很好
【解决方案2】:

您正在尝试将纯 JavaScript 对象呈现给 DOM。由于 React 组件也匹配 typeof Object 提供的类型检测开关而发生此错误

您可以访问此答案以深入了解正确的 React 组件检测:How to detect a React component vs. a React element?

回想起来,我会将此组件重写为如下内容:

import Icon from '@material-ui/core/Icon';

function isClassComponent(component) {
    return (
        typeof component === 'function' && 
        !!component.prototype.isReactComponent
    )
}

function isFunctionComponent(component) {
    return (
        typeof component === 'function' && 
        String(component).includes('return React.createElement')
    )
}

function isReactComponent(component) {
    return (
        isClassComponent(component) || 
        isFunctionComponent(component)
    )
}

const CustomIcon = ({ icon: ProvidedIcon }) => {
    // Order
    // 1. Handle React components first
    if (isReactComponent(ProvidedIcon)) {
        return <ProvidedIcon />
    }
    // Provide code to handle svg/string paths & a default case.
}

export default CustomIcon;

【讨论】:

    猜你喜欢
    • 2021-04-05
    • 2019-07-29
    • 2021-11-15
    • 1970-01-01
    • 2018-11-12
    • 2020-10-10
    • 2016-02-28
    • 1970-01-01
    • 2018-11-01
    相关资源
    最近更新 更多