React HOC 在一些但不是其他组件上工作
Posted
技术标签:
【中文标题】React HOC 在一些但不是其他组件上工作【英文标题】:React HOC working on some but not other components 【发布时间】:2020-07-28 12:49:35 【问题描述】:我正在使用 HOC 组件将操作绑定到许多不同类型的元素,包括 SVG 单元格,当 onClick 正常绑定时,它可以工作,但是当我使用我的 HOC 时,它会返回意外的结果。
最小可重现示例:https://codesandbox.io/s/ecstatic-keldysh-3viw0
HOC 组件:
export const withReport = Component => ( children, ...props ) =>
console.log(Component); //this only prints for ListItem elements for some reason
const dispatch = useContext(DashboardContext);
const handleClick = () =>
console.log('clicked!'); //even this wont work on some.
const report = props;
if (typeof report === "undefined") return false;
dispatch( type: SET_ACTIVE_REPORT, activeReport: report );
dispatch( type: TOGGLE_REPORT );
;
return (
<Component onClick=handleClick ...props>
children
</Component>
);
;
使用工作:
const ListItemWIthReport = withReport(ListItem); //list item from react-mui
items.map((item, key) => (
<ListItemWithReport report=item.report key=key button>
/* listitem children*/
</ListItemWithReport>
))
使用无效:
const BarWithReport = withReport(Bar); //Bar from recharts
bars.map((bar, index) => (
<BarWithReport
report=bar.report
key=index
dataKey=bar.name
fill=bar.fill
/>
))
ListItem 按预期 100% 工作,但是,条形图不会在 BarChart 内呈现。同样,使用 PieChart 时,单元格实际上会根据它们的值呈现正确的大小,但是,像“填充”这样的道具似乎不会传递下去。
我是否错误地使用了 HOC?我在 Charts 内部没有看到除 HOC 之外的选项,因为许多类型的元素将被视为无效 html?
【问题讨论】:
您如何在handleClick
中访问report
?
@rzwnahmd 我的错,我删除了它上面的行,因为我认为我没有使用它,代码破坏了道具,让我更新。
我认为children
在渲染范围内无效,因为Component
(HOC 的第一个参数)基本上已经是孩子了?这也是我可以(直观地)看到示例一和示例二之间的唯一区别。
@bbortt 是的,我也是这么想的,但是,即使你只返回<Component onClick=handleClick ...props />.
,它仍然不起作用
【参考方案1】:
您可能正在处理具有重要静态属性的组件,这些属性需要提升到包装的组件中,或者需要实现 ref 转发以便其父组件处理它们。将这些部件放在适当的位置很重要,尤其是在包装您不了解其内部结构的组件时。那个Bar
组件,例如does have some static properties。您的 HOC 正在让这些消失。
以下是提升这些静态成员的方法:
import hoistNonReactStatic from 'hoist-non-react-statics';
export const withReport = Component =>
const EnhancedComponent = props =>
const dispatch = useContext(DashboardContext);
const handleClick = () =>
const report = props;
if (typeof report === "undefined") return false;
dispatch( type: SET_ACTIVE_REPORT, activeReport: report );
dispatch( type: TOGGLE_REPORT );
;
return (
<Component onClick=handleClick ...props/>
);
;
hoistNonReactStatic(EnhancedComponent, Component);
return EnhancedComponent;
;
关于提升静态和引用转发的文档可以在这个handy guide to HOCs中找到。
可能有一些库可以为您处理所有这些细节。一个,addhoc
,是这样工作的:
import addHOC from 'addhoc';
export const withReport = addHOC(render =>
const dispatch = useContext(DashboardContext);
const handleClick = () =>
const report = props;
if (typeof report === "undefined") return false;
dispatch( type: SET_ACTIVE_REPORT, activeReport: report );
dispatch( type: TOGGLE_REPORT );
;
return render( onClick: handleClick );
);
当然,如果父组件通过类型显式检查子组件,那么你将根本无法使用 HOC。事实上,看起来 recharts 有这个问题。在这里你可以看到图表是defined in terms of child components,然后是searched for explicitly by type。
【讨论】:
您能否分叉最小可重现的示例并更新这些更改以显示它的工作原理?我已经在本地对提升非反应静态进行了更改,但没有产生任何影响。 我认为您是一种 SOL,因为 recharts 明确要求某些子组件类型,因此您的 HOC 不能用作交换替换。查看更新。我有解决方法的想法,请继续关注... 谢谢,我已经在 recharts 仓库上打开了一个问题,看看维护者是否还有其他要添加的内容。 所以我有 5 个不同的图表元素,需要将 onClick 绑定到一个系列,并且由于 HOC 不起作用,对于最小的代码复制,您有什么建议?【参考方案2】:我认为您的 HOC 无效,因为并非每个包装组件(例如 HTML 元素)基本上都是可点击的。也许这个片段可以澄清我想说的:
const withReport = Component => (props) =>
const handleClick = () => console.log('whatever')
// Careful - your component might not support onClick by default
return <Component onClick=handleClick ...props />
// vs.
return <div onClick=handleClick style=backgroundColor: 'green'>
<Component ...props />
props.children
</div>
// Your import from wherever you want
class SomeClass extends React.Component
render()
return <span onClick=this.props.onClick>this.props.children</span>
// vs.
return <span style=backgroundColor: 'red'>
// Careful - your imported component might not support children by default
this.props.children
</span>
const ReportedListItem = withReport(SomeClass)
ReactDOM.render(<ReportedListItem>
<h2>child</h2>
</ReportedListItem>, mountNode)
你可以有上部或下部(由vs.
分隔)但不能交叉。使用第二个返回(受控包装组件)的 HOC 肯定会更节省。
【讨论】:
“当一个onClick正常绑定时,它工作”,也不能用div包装组件,因为div不是SVG的有效子。【参考方案3】:我已经成功使用了 4 种方法来包装 Recharts 组件。
第一种方法
将组件包装在 HOC 中,并使用带有一些重载的 Object.Assign。这会破坏一些动画并且难以在线上使用活动点。 Recharts 在渲染组件之前会从组件中获取一些道具。所以如果 prop 没有传递到 HOC 中,那么它就不会正确渲染。
...
function LineWrapper(
dataOverload,
data,
children,
strokeWidth,
strokeWidthOverload,
isAnimationActive,
dot,
dotOverload,
activeDot,
activeDotOverload,
...rest
: PropsWithChildren<Props>)
const defaultDotStroke = 12;
return (
<Line
aria-label="chart-line"
isAnimationActive=false
strokeWidth=strokeWidthOverload ?? 2
data=dataOverload?.chartData ?? data
dot=dotOverload ?? strokeWidth: defaultDotStroke
activeDot=activeDotOverload ?? strokeWidth: defaultDotStroke + 2
...rest
>
children
</Line>
);
export default renderChartWrapper(Line, LineWrapper,
activeDot: <Dot r=14 />,
);
const renderChartWrapper = <P extends BP, BP = >(
component: React.ComponentType<BP>,
wrapperFC: React.FC<P>,
defaultProps?: Partial<P>
): React.FC<P> =>
Object.assign(wrapperFC, component);
if (defaultProps)
wrapperFC.defaultProps = wrapperFC.defaultProps ?? ;
Object.assign(wrapperFC.defaultProps, defaultProps);
return wrapperFC;
;
第二种方法
使用默认道具分配值。任何传入 HOC 的 props 都会被覆盖。
import XAxisProps from 'recharts';
import createStyles from '@material-ui/core';
import themeExtensions from '../../../assets/theme';
const useStyles = createStyles(
tickStyle:
...themeExtensions.font.graphAxis,
,
);
type Props = XAxisProps;
// There is no actual implementation of XAxis. Recharts render function grabs the props only.
function XAxisWrapper(props: Props)
return null;
XAxisWrapper.displayName = 'XAxis';
XAxisWrapper.defaultProps =
allowDecimals: true,
hide: false,
orientation: 'bottom',
width: 0,
height: 30,
mirror: false,
xAxisId: 0,
type: 'category',
domain: [0, 'auto'],
padding: left: 0, right: 0 ,
allowDataOverflow: false,
scale: 'auto',
reversed: false,
allowDuplicatedCategory: false,
tick: style: useStyles.tickStyle ,
tickCount: 5,
tickLine: false,
dataKey: 'key',
;
export default XAxisWrapper;
第三种方法
我不喜欢这个,所以我已经解决了这个问题,但是你可以扩展这个类。
export default class LineWrapper extends Line
render()
return (<Line ...this.props />
第四种方法
我没有一个简单的例子,但我总是渲染形状或孩子并提供帮助的功能。例如,对于条形单元格,我使用这个:
export default function renderBarCellPattern(cellOptions: CellRenderOptions)
const data, fill, match, pattern = cellOptions;
const id = _uniqueId();
const cells = data.map((d) =>
match(d) ? (
<Cell
key=`cell-$id`
strokeWidth=4
stroke=fill
fill=`url(#bar-mask-pattern-$id)`
/>
) : (
<Cell key=`cell-$id` strokeWidth=2 fill=fill />
)
);
return !pattern
? cells
: cells.concat(
<CloneElement<MaskProps>
key=`pattern-$id`
element=pattern
id=`bar-mask-pattern-$id`
fill=fill
/>
);
// and
<Bar ...requiredProps>
renderBarCellPattern(...cell details)
</Bar>
CloneElement 只是 Reacts cloneElement() 的个人包装器。
【讨论】:
以上是关于React HOC 在一些但不是其他组件上工作的主要内容,如果未能解决你的问题,请参考以下文章