我需要这些手工制作的组件吗(React.Fragment 将通过 props 和条件包装器)

Do I need these handmade components (React.Fragment that will passthrough props and conditionnal wrapper)

我觉得我只是重新发明了轮子,它可能不是圆的,因为我是 React 生态系统的新手。

我写了这些组件:

const ForwardPropsWrapper = ({ children, ...rest }) => {
    return React.Children.map(children, child => React.cloneElement(child, rest))
}

请注意,它只是将其 props 转发给它的 children。

const ConditionalWrapper = ({ condition, children, ...rest }) => {
    return (!condition || condition(rest)) ?
    React.Children.map(children, child => React.cloneElement(child, rest))
    : null;
}

condition 是一个函数,它传递了包装器的 props

我将它与 react-admin 一起使用,用两个字段组合替换 Datagrid 中的 ReferenceField :

<Datagrid rowClick="edit">
    {/*
    `Datagrid` will pass props to `ForwardPropsWrapper`
    */}
    <ForwardPropsWrapper label="User / Role">
        {/*
        Both `ConditionalWrapper`s will receive props passed by `Datagrid`
        through `ForwardPropsWrapper` and call the `condition` function
        with these props as argument
        */}
        <ConditionalWrapper condition={props=>props.record.RoleId}>
            <ReferenceField source="RoleId" reference="role">
                <TextField source="name" />
            </ReferenceField>
        </ConditionalWrapper>
        <ConditionalWrapper condition={props=>props.record.UserId}>
            <ReferenceField source="UserId" reference="user">
                <TextField source="email" />
            </ReferenceField>
        </ConditionalWrapper>
    </ForwardPropsWrapper>
</Datagrid>

为什么?

ForwardPropsWrapper 因为 react-adminDatagrid 期望每列 一个 child 并将道具传递给它(记录). A React.Fragment 不够好,因为它会吞掉道具。

ConditionalWrapper 是明确的,我需要根据 Datagrid 传递给它们的道具来显示两个组件之一。条件需要使用 Datagrid 传递给包装组件的道具进行评估,所以我不能在模板中使用简单的三元条件。

所以...我不敢相信这是要走的路。

有没有不用编写自定义组件就可以实现的方法?

我 运行 使用上述组件可能会遇到什么问题?

还望大家批评指正!

考虑到 <ForwardPropsWrapper label="User / Role"> 旨在生成 DRYer 代码,它的问题是它只影响它自己的 children。如果有嵌套元素或其他元素没有共同的直接 parent,它们将保持 WET。

视情况可以写成:

const commonProps = { label: 'User / Role' };

...

<Foo {...commonProps} foo={'bar'} />
<Foo {...commonProps} foo={'baz'} />

或作为 HOC:

const withLabel = (Comp, label) => props => <Comp {...props} label={label}/>

const FooWithUserRoleLabel = withLabel(Foo, 'User / Role');

...

<FooWithUserRoleLabel foo={'bar'} />
<FooWithUserRoleLabel foo={'baz'} />

或者只是保持湿润,因为常见的 label="User / Role" 属性不会使代码比其他解决方案更难阅读或更笨重。

至于ConditionalWrapper,已经有三元和short-circuit求值了,尽量使用:

{ condition && <Foo /> }

{ condition ? (
  <Bar />
) : (
  <Baz />
)}

这里的实际问题是Datagrid用来向children提供数据的配方是不灵活的,遍历children并为他们提供额外的道具。这使得在需要处理 props 时需要繁琐的包装器。

实现相同目标的更灵活和常用的模式是 render prop,或者更具体地说,它的特殊情况,函数作为 child.可以提供 Datagrid 的包装器以代替原始组件使用:

const DatagridWrapper = (children, ...props) => {
  const render = React.Children.only(children);
  const Body = props => render(props);

  return <Datagrid {...props}><Body/></Datagrid>;
};

所有网格数据都在可以任何方式处理的回调中接收,并呈现 return 个元素。

<DatagridWrapper rowClick="edit">
  {gridData => <>
    {gridData.record.RoleId && (
      <ReferenceField label="User / Role" source="RoleId" reference="role">
        <TextField source="name" />
      </ReferenceField>
      ...
  </>}
</DatagridWrapper>

好吧,我说这些都没用是对的。只需要创建一个自定义 Field 组件。

<Datagrid rowClick="edit">
    <TextField source="id" />
    <DualReferenceField label="User / Role" />
    <TextField source="action" />
    <TextField source="subject" />
    <TextField source="criteria" />
    <TextField source="name" />
</Datagrid>
const DualReferenceField = props => {
    const hasRole = props.record.RoleId;
    const hasUser = props.record.UserId;
    return (
        <>
        { hasRole ? 
        <ReferenceField source="RoleId" reference="role" {...props}>
            <FunctionField render={(record) => {
                return (
                    <span>{record.name} <small>(role)</small></span>
                )
            }} />
        </ReferenceField>
        : null }
        { hasUser ?
        <ReferenceField source="UserId" reference="user" {...props}>
            <TextField source="email" />
        </ReferenceField>
        : null }
        </>
    )
}