问题描述
我经常发现自己创建或使用组件,其中回调用于创建组件树的一部分,如以下两个简化示例:
// Example 1: Render an array of components
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <li key={item.id} style={{ paddingTop: item.paddingTop }}>{item.label}</li>)}
</ul>
);
// Example 2: Use render props
const ExampleComponent2 = () => (
<Dialog
renderHeader={useCallback((dialogCtx) => 'Header',[])}
renderBody={useCallback((dialogCtx) => <button onClick={() => dialogCtx.closeDialog(null)}>Test</button>,[])}
/>
);
如您所见,两种情况下的回调结果都取决于回调参数。为了避免在每次渲染时创建新的 style
对象(在示例 1 中)和新的 onClick
函数(在示例 2 中),我应该将它们包装在 useMemo
/{ {1}},但这在这里是不可能的,因为它们是在回调内部创建的,而不是在组件的根级别上创建的。
useCallback
您已经在这个简化的示例中看到了如何以这种方式拆分应用程序会创建大量额外的代码并使应用程序更难阅读。在更复杂的场景中,父组件的大量 props 需要在子组件中重用,子组件会变得更加庞大,特别是当还需要定义 prop 类型时。
在我看来,这两个示例都是相当常见的用例,因为回调是在 React 中从数组生成组件列表的唯一方法,而渲染道具是更复杂组件中的常见模式。我想知道:
- 有什么办法可以在不将回调结果拆分为单独的组件的情况下编写上述示例?例如直接在回调中使用钩子的一些方法。
- 是否有其他模式可以将数组映射到不依赖回调的组件列表,以便可以使用钩子?
- 是否有替代模式来渲染 props 以便更轻松地使用钩子?
更新:为了清楚起见,我正在寻找一种编程模式,而不是上面简化示例代码中的具体问题。
解决方法
我不明白示例 1 有什么问题。
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <li key={item.id} style={{ paddingTop: item.paddingTop }}>{item.label}</li>)}
</ul>
);
这在 React 中完全没问题。在您看到 style
道具导致性能问题之前,您不应尝试对其进行优化。
带有钩子的版本,以防你真的需要它:
const ExampleComponent1Item = ({ item }) => {
const style = useMemo(() => ({ paddingTop: item.paddingTop }),[item.paddingTop]);
return (<li style={style}>{item.label}</li>);
};
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <ExampleComponent1Item key={item.id} item={item}/>)}
</ul>
);
同样适用于示例 2:
const ExampleComponent2 = () => {
const renderHeader = useCallback((dialogCtx) => 'Header',[]);
const renderBody = useCallback((dialogCtx) => <button onClick={() => dialogCtx.closeDialog(null)}>Test</button>,[]);
return (<Dialog renderHeader={renderHeader} renderBody=renderBody}/>);
};
如果您想避免每次调用 onClick
时都重新创建 renderBody
函数,您可以这样做:
const ExampleComponent2Body = {( dialogCtx }) => {
const onClick = useCallback(() => dialogCtx.closeDialog(null),[dialogCtx.closeDialog]);
return (<button onClick={onClick}>Test</button>);
};
const ExampleComponent2 = () => {
const renderHeader = useCallback((dialogCtx) => 'Header',[]);
const renderBody = useCallback((dialogCtx) => <ExampleComponent2Body dialogCtx={dialogCtx}/>,[]);
return (<Dialog renderHeader={renderHeader} renderBody=renderBody}/>);
};