在一个组件中,使用一个还是多个useEffect钩子更好?

285

我有一些副作用要应用在我的React组件中,并想知道如何组织它们:

  • 使用一个单独的useEffect
  • 或者使用多个useEffect

从性能和架构方面来看,哪个更好?


这可能取决于您的使用情况,但我将编辑您的问题,以使其清楚地涵盖一般建议。 - AncientSwordRage
3个回答

408

您需要遵循的模式取决于您的用例。

首先:您可能会遇到一种情况,需要在初始挂载期间添加事件侦听器并在卸载时将其清除,另一种情况是特定侦听器需要在属性更改时清除并重新添加。

在这种情况下,最好使用两个不同的useEffect,以保持相关逻辑在一起,并获得性能上的好处。

useEffect(() => {
   // adding event listeners on mount here
   return () => {
       // cleaning up the listeners here
   }
}, []);

useEffect(() => {
   // adding listeners everytime props.x changes
   return () => {
       // removing the listener when props.x changes
   }
}, [props.x])

第二点:当任何状态或属性从一个定义好的集合中发生变化时,您需要触发一个API调用或其他副作用。在这种情况下,使用单个useEffect与相关依赖项进行监控会更好。

useEffect(() => {
    // side effect here on change of any of props.x or stateY
}, [props.x, stateY])

第三点:您需要为不同的更改集分别设置副作用。在这种情况下,将相关的副作用分离到不同的useEffect中。

Translated text:

第三点:您需要为不同的更改集分别设置副作用。在这种情况下,将相关的副作用分离到不同的useEffect中。

useEffect(() => {
   // some side-effect on change of props.x
}, [props.x])

useEffect(() => {
   // another side-effect on change of stateX or stateY 
}, [stateX, stateY])

1
那么在上面第二个和第三个示例之间有没有一个折中方案呢?当状态/属性的子集发生更改时运行逻辑,但每个子集都需要运行单独的逻辑以及一些共同的代码需要运行时,您会怎么做?您不会使用 [](因为它仍然只是等待更改的状态/属性的 _子集_),但您也想重用代码。您是否使用单独的 useEffect 并将共享代码放入它们各自调用的函数中? - ecoe
10
如下面的答案所建议的那样,React团队建议根据关注点将钩子函数分开,因此您可以将其拆分为多个useEffect调用。 - hakazvaka
2
它们总是按照定义的顺序被触发吗?即,effect1 总是先被调用,然后是 effect2 吗? - computrius
4
是的,React将按照它们被指定的顺序应用组件使用的每个effect。 - August Janse
1
如果我有多个“componentDidMount”效果(空数组[]),但它们执行非常不同的操作,那么我应该将它们放在单个useEffect中还是多个? - Dror Bar

91

3
我知道,但如果你解释一下为什么关注点分离很重要,以及它是一个架构上的概念还是性能上的概念,那会让你的回答更好。 - AncientSwordRage

23

有多个 useEffect 是完全正常的

以下是我的其中一个设置示例:

/*
 * Backend tags list have changed add the changes if needed
 */
useEffect(() => {
    setTagsList(setTagsAdded);
}, [setTagsAdded]);

/*
 * Backend files have changed add the changes if needed
 */
useEffect(() => {
    for (let i = 0; i < changedFilesMeta.length; i += 1) {
        // Is the list item value changed
        if (changedFilesMeta[i].id === currentEditableFile.id) {
            unstable_batchedUpdates(() => {
                setTags(changedFilesMeta[i].tags ? changedFilesMeta[i].tags : []);
            });
        }
    }
}, [changedFilesMeta]);

/*
 * Reset when user select new files using the filepicker
 */
useEffect(() => {
    if (setNewFiles.length > 0) {
        unstable_batchedUpdates(() => {
            setCurrentFile(null);
            setDescription('');
            setTitle('');
            setTags([]);
        });
    }
}, [setNewFiles]);

/*
 * User selecet to edit a file, change to that file
 */
useEffect(() => {
    // When user select a file to edit it
    if (currentEditableFile && currentEditableFile !== theCurrentFile) {
        setCurrentFile(currentEditableFile);
        unstable_batchedUpdates(() => {
            setDescription(currentEditableFile.description);
            setTitle(currentEditableFile.title);
            setTags(currentEditableFile.tags);
        });
    }
}, [currentEditableFile]);

你能添加更多的代码吗?以此来清楚 setTags 正在更新哪个状态位吗? setTagsAdded 是函数还是对象?另外,你是如何确保这种方式比一个大的 useEffect 的性能和架构都更好呢? - AncientSwordRage
1
我认为包含这段代码,尤其是没有上下文的情况下,对问题并没有帮助或相关性。 - Bricky
1
@Bricky 无论如何,剥离所有代码并添加引用会改变答案太多,无法证明其正确性。 - AncientSwordRage
引用是来自链接的相关部分。仅包含链接是低效的回答。 - Bricky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接