我觉得我只是重新发明了轮子,而且可能不是圆圆的,因为我是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
传递给包装组件的道具进行评估,因此我不能在模板中使用简单的三元条件。
所以...我不敢相信这是要走的路。
是否有一种无需编写自定义组件即可实现此目标的方法?
上述组件可能会遇到什么问题?
请批评指教!
答案 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 }
</>
)
}