【问题标题】:Does the dependency array in React hooks really need to be exhaustive?React hooks 中的依赖数组真的需要详尽无遗吗?
【发布时间】:2021-03-02 03:16:47
【问题描述】:

根据React docs

效果函数中引用的每个值也应该出现在依赖项数组中

如果我的效果函数从外部范围引用了一些变量,但我只想在其中一个发生变化时执行它,为什么我需要在依赖数组中指定所有其他变量?是的,如果其他变量发生变化,闭包将变得陈旧,但我不在乎,因为我还不需要调用该函数。当我关心的变量发生变化时,可以调用具有当时值的新闭包。我错过了什么?

这是一个工作示例(据我所知),其中 useEffect 依赖数组并不详尽:

import React, { useEffect, useState } from "react";

const allCars = {
    toyota: ["camry", "corolla", "mirai"],
    ford: ["mustang", "cortina", "model T"],
    nissan: ["murano", "micra", "maxima"],
};

function CarList() {
    const [cars, setCars] = useState([]);
    const [brand, setBrand] = useState("toyota");
    const [filterKey, setFilterKey] = useState("");

    useEffect(() => {
        // I don't want to run this effect when filterKey changes because I wanna wrap that case in a timeout to throttle it.
        setCars(allCars[brand].filter(model => model.startsWith(filterKey)));
    }, [brand]);

    useEffect(() => {
        // This effect is only called when filterKey changes but still picks up the current value of 'brand' at the time the function is called.
        const timeoutId = setTimeout(() => {
            setCars(allCars[brand].filter(model => model.startsWith(filterKey)));
        }, 500);
        return () => clearTimeout(timeoutId);
    }, [filterKey]);

    const handleChangeFilterKey = event => {
        setFilterKey(event.target.value);
    };

    return (
        <div>
            {`${brand} cars`}
            <div>Select brand</div>
            <input type="radio" value="toyota" checked={brand === "toyota"} onChange={() => setBrand("toyota")} />
            <input type="radio" value="ford" checked={brand === "ford"} onChange={() => setBrand("ford")} />
            <input type="radio" value="nissan" checked={brand === "nissan"} onChange={() => setBrand("nissan")} />
            <div>Filter</div>
            <input label="search" value={filterKey} onChange={handleChangeFilterKey} />
            <ul>
                {cars.map(car => (
                    <li>{car}</li>
                ))}
            </ul>
        </div>
    );
}

上面的例子有什么陷阱吗?

【问题讨论】:

  • 不,代码可以在不详尽的情况下工作 - 但通常建议详尽。
  • 不,一点也不。实际上,您可以使用完全空的依赖数组来指示效果应该在组件挂载时只运行一次。
  • 所以这两个效果都会运行第一个渲染,对吧?不是终端,但效率低下......

标签: reactjs react-hooks


【解决方案1】:

是的,你应该一直遵循这条规则,当你发现你的代码因为遵循它而中断时,这意味着没有遵循良好的做法。这就是这条规则的意思,确保你设计好代码。


我想在您的情况下,代码如下所示:

  const Test = () => {
   const [wantToSync] = useState(0)
   const [notWantToSync] = useState(0) // notWantToSync also might come from props, i'll use state as example here
   useEffect(() => {
    fetch('google.com', {
      body: JSON.stringify({wantToSync, notWantToSync})
    }).then(result => {
      // ...
    })
   }, [wantToSync]) // component is supposed to be reactive to notWantToSync, missing notWantToSync in dep is dangerous
 }

如果notWantToSync 被定义为组件的状态,则组件应该对其进行反应,包括useEffect。如果这不是您想要的,notWantToSync 不应该是从一开始就存在的状态。

const Test = () => {
  const [wantToSync] = useState(0)
  const notWantToSyncRef = useRef(0) // hey I don't want notWantToSync to be reactive, so i put it in useRef
  useEffect(() => {
    fetch('google.com', {
      body: JSON.stringify({wantToSync, notWantToSync: notWantToSyncRef.current})
    }).then(result => {
      // ...
    })
  }, [wantToSync, notWantToSyncRef]) // yeah, now eslint doesn't bother me, and notWantToSync doesn't trigger useEffect anymore
}

通常你不需要在 useEffect 中使用 if else 来停止重新渲染,除了 useRef 之外,还有其他方法,如 useMemouseCallback 可以做类似的事情,尽管它们有不同的使用上下文.


我看到你在新例子中的挣扎,所以你想做一个油门,如果filterKey被添加到第一个useEffect dep,油门就会被打破。我的观点是,当你发现自己处于这种情况时,这通常意味着有更好的做法(eslint 详尽的帮助来识别它),例如,将油门逻辑提取到挂钩:https://github.com/streamich/react-use/blob/master/src/useThrottle.ts

详尽的 deps 并不是必须遵循的,它只是确保代码设计良好的一种好习惯。上面的例子证明了

【讨论】:

  • 感谢您的回复。这并不能比文档中的引用更能回答问题。当您说它将确保我不会错过必要的依赖项时,您是在假设我在质疑。鉴于我所说的,为什么依赖项是必要的。对我来说,它确实很痛。在我希望仅在一个变量更改时调用我的函数的情况下,现在它会在任何变量更改时调用,因此我必须添加额外的检查并使逻辑复杂化。此外,钩子现在需要扫描更长的数组,这在大多数情况下可能没什么大不了的,但为什么要添加 [cont...]
  • [...cont] 比必要的计算量更多。
  • 嘿@NissanMurano,对不起,我已经添加了更多细节
  • 再次感谢您的详细回答。我看到您添加了一个解决方案,用于指定不触发效果功能的依赖项。那肯定会起作用,但它真的与不指定它们有什么不同吗?下面的值仍在变化,useEffect 无法检测到它,因为它被包裹在一个对象中。类似于它无法检测到根本没有指定的变量。
  • 您好,感谢您提供示例,事实上,这个示例证明了穷举 deps 规则是有用的,因为它可以确保您不会陷入这种情况。已在答案中包含更多详细信息
【解决方案2】:

在你的情况下,没有副作用,它只是从状态派生的数据,所以你应该使用不同的工具而不是useEffect

第一个过滤器可以通过按键获取值来实现:

const carsByBrand = allCars[brand];

这个操作的复杂性是恒定的,所以记忆会太昂贵。

第二个过滤器去抖动,但您可能想要memoize它,因为过滤具有线性复杂性。出于这些目的,您可以使用例如 useDebouncedMemo 钩子:

const filteredCars = useDebouncedMemo(() => {
   return carsByBrand.filter(model => model.startsWith(filterKey)
}, [filterKey, carsByBrand], 500)

在这种情况下,钩子的依赖关系是详尽的,逻辑更具声明性。

【讨论】:

    猜你喜欢
    • 2020-11-22
    • 2020-12-07
    • 2019-11-26
    • 1970-01-01
    • 2023-01-16
    • 2020-04-03
    • 2020-06-18
    • 2019-10-17
    • 2021-10-21
    相关资源
    最近更新 更多