为什么不建议在React函数组件中生成子组件

418 阅读3分钟

在开发React组件时,会遇到一些情况需要动态地生成子组件。例如,在渲染列表或表格数据时,你可能需要动态地生成多个子组件,这些子组件的数量和内容会根据数据的不同而变化。然而,在动态生成子组件时,需要注意一些问题,例如性能和复杂性。

在本文中,我们将讨论在React组件中动态生成子组件时的最佳实践,并提供一些技巧来优化性能和减少复杂性。

首先我们来看一段错误示范:

import { useState } from 'react';

export const App3 = () => {
  const [value, setValue] = useState('');
  const Child = () => (
    <input value={value} onChange={(e) => setValue(e.target.value)} />
  );
  return (
    <div>
      <Child />
    </div>
  );
};

乍一看,似乎没问题。但是当我们运行时会发现,每次输入后,input都会失去焦点! 问题出在子组件Child上。在React中,当一个组件的props或state发生改变时,会自动重新渲染该组件及其子组件。在这个例子中,由于Child组件是一个函数组件,每次父组件重新渲染时都会重新定义一个新的Child组件实例。实际上,如果我们在Child中,加入

useEffect(()=>{
    console.log('mounted')
},[])

可以看到每次输入后,都将打印‘mounted’,这证明,Child组件随着父组件re-render在不断的创建销毁再创建,从而导致input每次输入完后都失焦。只不过得益于超快的引擎,input每次创建我们很难感知到罢了。

为了避免这个问题,可以将子组件Child定义为一个普通的函数而不是一个内联函数组件,这样可以确保每次渲染时使用相同的组件实例。例如:

import { useState } from 'react';

const Child = ({value,setValue}) => (
  <input value={value} onChange={(e) => setValue(e.target.value)} />
);

export const App = () => {
  const [value, setValue] = useState('');
  return (
    <div>
      <Child
         value={value}
         setValue={setValue}
      />
    </div>
  );
};

这样,每次重新渲染时都会使用相同的Child组件实例,避免了不必要的性能问题。

或者可以直接将子组件插入到父组件DOM树中:

import { useState } from 'react';

export const App = () => {
  const [value, setValue] = useState('');
  const child = () => (
    <input value={value} onChange={(e) => setValue(e.target.value)} />
  );
  //或者: const child = <input value={value} onChange={(e) => setValue(e.target.value)} />;
  
  return <div>{child()}</div>;
};

这两种写法没有区别,当child中输入框的值改变,触发父组件re-render,但是父组件的DOM树并没有改变,因此input不会失去焦点。

总结

当在 React 函数组件中生成子组件时,需要考虑到以下几个因素:

  1. 性能问题:在函数组件中生成子组件可能会导致性能问题。这是因为在每次函数组件重新渲染时,都会生成新的子组件实例,如果子组件是有状态的,那么每次都会重新初始化状态,这会增加渲染时间和内存消耗。
  2. 组件复用问题:将子组件的生成逻辑写在父组件中,可能会导致组件的复用性变差。如果子组件被多个父组件使用,那么就需要在多个父组件中重复写生成逻辑,这样会导致代码冗余。
  3. 难以维护问题:将子组件的生成逻辑写在父组件中,会导致代码变得复杂难以维护。在一个组件中,如果有太多的逻辑,那么这个组件就很难被理解和修改。

因此,如果子组件是纯展示组件,且不需要状态管理,那么可以在父组件中生成子组件。但如果子组件需要有状态管理,或者需要在其他组件中复用,那么最好将子组件的生成逻辑封装在一个单独的组件中。