首页 > 解决方案 > React hooks - useEffect 详尽的 deps - 对 location.hash 的循环依赖

问题描述

我有一个useEffect读取location.hash并基于其他一些依赖项,将更改哈希。它看起来像这样:

useEffect(() => {
    const hashAlreadyPresent = () => {
      const hashArr = history.location.hash.split('#');
      return hashArr.includes(hashId);
    };

    const addToHash = () => {
      return history.location.hash.concat(`#${hashId}`);
    };

    const removeFromHash = () => {
      const hashArray = history.location.hash.split('#').filter(hashStr => hashStr);
      const indexOfHashId = hashArray.indexOf(hashId);
      (indexOfHashId !== -1) && hashArray.splice(indexOfHashId, 1);

      return hashArray;
    };

    // if hashId props is present then attach hash in route
    hashId && !hashAlreadyPresent() && history.push({
      hash: `${hashAlreadyPresent() ? '' : addToHash()}`,
      search: history.location.search,
    });

    return () => {
      // remove hashId only, retain any other hash if present
      const hashArray = removeFromHash();
      hashId && hashAlreadyPresent() && history.replace({
        hash: hashArray.join('#'),
        search: history.location.search,
      });
    };
  }, [history, hashId, history.location.hash, history.location.search]);
    

history来自 React Router 的位置。

逻辑是,一旦组件在屏幕上(已安装),它会向 URL 添加一个哈希,一旦它被卸载,它将从 url 中删除哈希。

当然,就 useEffect 而言,它转换为:如果任何依赖项发生变化,则先前的效果将被清除,并会生成一个新的效果实例。有效的 deps 规则帮助了我,因为之前我错过了这个钩子应该被清理并在hashId更改时重新运行的事实。

现在,我们应该history.location.hash对详尽的 deps 有依赖,但问题是每次我hash从钩子内部更改时,钩子都会再次运行(前一个实例将清理并hash再次更改),这将导致无限更新类型的情景。

注意:我知道这可以通过关闭穷举规则并排除history.location.hash依赖项来实现,但想弄清楚重构/分解的任何可能性useEffect,这样就可以在不关闭它的情况下解决这个问题。

需要注意的另一件事是,如果我添加history为依赖项(我必须这样做,因为我使用的是 from 的方法history),那么规则不会要求我显式添加嵌套的依赖项(history.lcoation.searchhistory.location.hash),但是应该添加那些,因为history对象将保持不变,但嵌套对象会在 url 更改时更改。这与您将完整props对象指定为依赖项而不是仅指定所需的特定嵌套属性的用例相同。

我是否应该根据位置更改在我的 useEffect 中有一个条件,这可以以某种方式告诉我位置是否从钩子内部更改,所以不做任何事情?

我是否应该以不同的方式解构和指定依赖项,以便在从效果中更改 location.hash 时效果不会运行?

注意: 在 github 上对此进行了讨论。得到了更多的见解。 https://github.com/facebook/react/issues/19636

标签: javascriptreactjsreact-routerreact-hooksuse-effect

解决方案


当指定非空依赖数组时,添加到依赖数组的任何值都会导致清理函数首先运行(第一次渲染除外),然后是效果函数(卸载期间除外)。要决定一个值是否应该进入依赖数组,请尝试为该值回答这个问题:

更新此值时,是否应再次运行效果,例如:

  • 观察到预期的效果
  • 如果需要,将清除之前所做的任何潜在更改
  • 没有引入由于过时的引用导致的错误?

如果上述任何一点的答案是肯定的,那么该值将进入依赖数组。

我们现在可以为函数中使用的所有值回答上述问题useEffect

  • hashId:的。这是效果的主要驱动因素,每次此值更改时,URL 都应反映更改。这成为效果的真实来源。因此,这是确保观察到预期效果所必需的。此外,这也是清理 previous 所必需的,hashId因为清理函数需要对 previous 的引用hashId
  • history:的。我想由于这是由反应路由器提供的,因此引用不应在整个组件的生命周期中更改。从这个意义上说,在这里添加它的唯一目的是满足 lint 规则,没有实际影响(除了额外的引用检查)。但是,如果它确实发生了变化,效果函数将对它有一个过时的引用,这可能会导致错误。必须注意这一点。
  • history.location.search:没有。这与主要效果无关,因为只hashId需要确保观察到所需的效果。也没有过时引用的危险,因为它总是从history对象中读取。由于history对象是可变的并且每次都使用最新值更新,并且已经是依赖数组的一部分,history.location.search因此可以安全地省略。*
  • history.location.hash:,对于与 for 相同的论点history.location.search。此外,它始终hashId决定history.location.hash应该是什么,因此不应该使用对此值的更新来重新运行效果。

最终的依赖数组就是[hashId, history]. **


* 注意不要从清理函数中提取searchhistory.location使用search,因为这将是一个过时的引用

** 注意routeModal在效果体中使用,如果需要,这也必须是依赖数组的一部分


推荐阅读