我是否需要这些手工组件(将穿过道具和条件包装器的React.Fragment)?

时间:2019-01-26 10:32:09

标签: javascript reactjs react-admin

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

我已经写了这些组件:

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

请注意,它只是将道具转发给孩子。

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

condition是一个函数,它已传递包装器的道具

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

<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-admin的{​​{1}}每列期望有一个一个子代,并将道具传递给它(记录)。 Datagrid不够好,因为它会吞下道具。

React.Fragment是明确的,我需要根据ConditionalWrapper传递它们的道具来展示两个组成部分之一。该条件需要使用Datagrid传递给包装组件的道具进行评估,因此我不能在模板中使用简单的三元条件。

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

是否有一种无需编写自定义组件即可实现此目标的方法?

上述组件可能会遇到什么问题?

请批评指教!

2 个答案:

答案 0 :(得分:2)

考虑<ForwardPropsWrapper label="User / Role">旨在产生DRYer代码,它的问题在于它仅影响自己的子代。如果存在嵌套的或其他没有共同直接父元素的元素,它们将保留在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'} />

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

对于ConditionalWrapper,已经存在三元和短路评估,请尽可能使用它们:

{ condition && <Foo /> }

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

这里的实际问题是Datagrid用于向儿童提供数据的配方不灵活,无法遍历儿童并为他们提供额外的道具。这使得在需要处理道具的情况下需要繁琐的包装器。

render prop,或更具体地讲,其特殊情况是作为孩子的功能,是实现相同目标的一种更为灵活且常用的模式。可以提供Datagrid的包装而不是原始组件来使用:

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

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

所有网格数据都是在回调中接收的,该回调可以任何方式处理并返回要渲染的元素。

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

答案 1 :(得分:1)

我说对了,这都是没用的。只需创建一个自定义字段组件即可。

<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 }
        </>
    )
}