在 render() 函数中定义函数组件是一种反模式吗?

IT技术 javascript reactjs react-native
2021-04-17 10:07:28

我想知道它是否是一种反模式,或者它是否会以某种方式影响组件以执行以下操作:

render() {
  const MyFuncComponent = ({ prop1, prop2 }) => (
    // code here
  )

  return (
    <div>
      <MyFuncComponent prop1={something} prop2={else} />
    </div>
  )
}
2个回答

是的,这是一种反模式,原因与我们不应该在render.

不要在渲染方法中使用 HOC

React 的 diffing 算法(称为 reconciliation)使用组件标识来确定它是应该更新现有的子树还是丢弃它并安装一个新的子树。如果返回的组件与前一个渲染的组件render相同 ( ===),React 会通过将其与新的比较来递归更新子树。如果它们不相等,则完全卸载前一个子树。

通常情况下,您不需要考虑这一点。但这对 HOC 很重要,因为这意味着您不能在组件的 render 方法中将 HOC 应用于组件:

render() {
  // A new version of EnhancedComponent is created on every render
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // That causes the entire subtree to unmount/remount each time!
  return <EnhancedComponent />;
}

这里的问题不仅仅与性能有关——重新挂载组件会导致该组件及其所有子组件的状态丢失。

这意味着,新的组件将出现在阵营树(可与探索react-devtools),但它不会保留任何状态和生命周期方法,如componentDidMountcomponentWillUnmountuseEffect总是会得到所谓的每个渲染周期。

解决方案

由于动态创建组件可能是有原因的,这里有一些常见的模式可以避免这些陷阱。

在外部定义新组件

要么在它自己的文件中,要么直接在父组件的定义之上。将任何变量作为 props 传递,而不是使用父组件范围来访问值。

const MyFuncComponent = ({ prop1, prop2 }) => <>{/* code here */}</>;

const MyComponent = props => (
  <div>
    {props.list.map(({ something, thing }) => (
      <MyFuncComponent prop1={something} prop2={thing} />
    ))}
  </div>
);

辅助功能

可以直接在另一个组件中定义使用返回 JSX 的常规函数它不会作为新组件出现在 React 的树中,只会出现它的结果,就好像它是内联的一样。

这样,props.itemClass除了您想要的任何其他参数之外,我们还可以使用来自封闭范围的变量(如下例所示)。

const MyComponent = props => {
  // Looks like a component, but only serves as a function.
  const renderItem = ({ prop1, prop2 }) => (
    <li className={props.itemClass}> {/* <-- param from enclosing scope */}
      {prop1} {prop2} {/* other code */}
    </li>
  );

  return <ul>{props.list.map(renderItem)}</ul>;
};

它也可以在组件外部定义,因为它非常灵活。

const renderItem = (itemClass, { prop1, prop2 }) => (
  <li className={itemClass}>
    {prop1} {prop2} {/* other code */}
  </li>
);

const MyComponent = props => (
  <ul>
    {props.list.map(item => renderItem(props.itemClass, item))}
  </ul>
);

但在这一点上,我们应该只定义一个 React 组件,而不是用一个函数来伪造它。以可预测的方式使用 React 并发挥其全部潜力。

内联逻辑

在条件或map回调中内联 JSX 是很常见的

const MyComponent = ({ itemClass }) => (
  <ul>
    {props.list.map(({ something, thing }) => (
      <li className={itemClass}>
        {something} {thing} {/* other code */}
      </li>
    ))}
  </ul>
);

如果我们发现自己到处复制粘贴相同的内联 JSX,那么可能是时候将它包装在自己的可重用组件中了。

我认为一般人们会避免在渲染中定义函数,但根据这篇博客文章,这不一定是一个坏习惯。博客文章重点介绍了在渲染中定义的内联事件处理函数,但我猜它适用于渲染中定义的任何函数。在 render 中定义函数意味着每次调用 render 时都会有重新定义它们的开销,但这可能不会产生明显的性能差异,具体取决于您的组件。

对于您给出的特定示例,我建议不要在渲染中定义另一个react组件。如果您确实在渲染中定义了任何函数,它们应该与渲染正在执行的操作保持一致。在 render 中定义另一个组件或添加一堆函数会使代码变得笨拙且难以理解。