javascript - 第一个更新第二个依赖项的相关 useEffects 会导致奇怪的行为

标签 javascript reactjs dom async-await react-hooks

编辑代码块以包含来自已接受答案的控制台日志。

我有一个搜索栏组件,我已完全将其精简为最简单的形式:

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

const resultTypes = ["categories", "submissions", "users"];

const SearchBar = () => {
  const [results, setResults] = useState([]);
  const [selectedResultType, setSelectedResultType] = useState(resultTypes[0]);

  console.log("render --------");

  console.log("selectedResultType: " + selectedResultType);

  const selectedResultTypeRef = useRef(null);
  console.log(
    "selectedResultTypeRef.current: " + selectedResultTypeRef.current
  );

  const fetchResults = async () => {
    console.log("fetching...");
    const response = await axios.get(
      "https://jsonplaceholder.typicode.com/todos/3"
    );
    setResults(response.data);
    console.log("setResults: ", response.data);
  };

  useEffect(() => {
    console.log("effect 1 ---------");
    selectedResultTypeRef.current = selectedResultType;
    console.log(
      "selectedResultTypeRef.current: " + selectedResultTypeRef.current
    );
  }, [selectedResultType]);

  useEffect(() => {
    console.log("effect 2 ---------");
    // No need to debounce fetch when changing result type
    fetchResults();
  }, [selectedResultTypeRef.current]);

  return (
    <div className="SearchBar__container">
      <div className="SearchBarResults__types">
        {resultTypes.map((resultType) => (
          <button
            key={resultType}
            onClick={() => {
              console.log("click " + resultType + " -------");
              setSelectedResultType(resultType);
            }}
          >
            {resultType}
          </button>
        ))}
      </div>
    </div>
  );
};

export default SearchBar;

当我在 3 个按钮(类别、提交和用户)之间单击时,我希望 selectedResultType 发生变化,然后它会更改 selectedResultTypeRef.current,以便我可以将其传递到一个记忆化、去抖动的函数中。我删除了所有这些,因为它们不相关,但这就是我需要使用 refs 而不仅仅是组件状态的原因。

这是我的问题:当我调用异步函数并等待设置结果的响应时,似乎 selectedResultTypeRef.current 仅每隔一次单击更改(或被识别为更改)。我尝试了单击这 3 个按钮的不同组合,每次单击都会触发调用 fetchResults 的 Hook 。

这是使用上述代码的控制台输出: enter image description here

奇怪的是,它在错过前一个引用更改的提取后提取了两次。我实在不知道这是为什么。同样令人好奇的是,当我用一个简单的 console.log(response.data) 替换 setResults 行时,它永远不会错过任何一个钩子(Hook)。这是控制台输出: enter image description here

我怀疑 setResults 更新会以某种方式触发 dom 的重新渲染(即使我在这个简化版本中的任何地方都没有使用 results 状态) ,并且它会干扰 selectedResultTypeRef 被识别为由 useEffect Hook 捕获的更新。我不知道为什么每次更新都会如此,但我至少验证了其一致的模式。

除此之外,我完全不知所措。我不知道还能尝试什么。

最佳答案

在这里,我正在回答“为什么会发生这种情况?”的问题。而不是“如何构建具有去抖功能的搜索栏?”因为故意省略了细节。

重要的是要记住:

  • useState 调用 setter如果数据发生更改,则会导致重新渲染。尽管事实上数据并未被使用。
  • useEffect 时,效果会在渲染期间检查其依赖项列表。被称为。
  • 那些依赖关系已更改的效果将在渲染阶段之后执行。

现在假设所有内容都已加载。 selectedResultType === "categories"selectedResultTypeRef.current === "categories" .

  1. 点击submissions按钮。 setSelectedResultType被调用并开始重新渲染。
  2. 检查依赖关系:selectedResultType已更改为submissions所以effect 1将在渲染后调用。 selectedResultTypeRef.current还没有改变,仍然是"categories" ,所以effect 2不会被执行。 这就是没有重新获取的原因。
  3. effect 1被执行。现在selectedResultType === "submissions"selectedResultTypeRef.current === "submissions" .
  4. 点击users按钮 -> 重新渲染。
  5. 检查依赖关系:effect 1将被调用。 selectedResultTypeRef.current已从 "categories" 更改为至"submissions"所以effect 2也会被执行。
  6. effect 1被执行。现在selectedResultType === "users"selectedResultTypeRef.current === "users" .
  7. effect 2被执行 -> 重新获取 -> setResults -> 重新渲染。
  8. 检查依赖关系:effect 1不会被调用。但是effect 2会被调用,因为 selectedResultTypeRef.current已从 "submissions" 更改为至"users" .
  9. effect 2被执行 -> 重新获取 -> setResults -> 重新渲染。
  10. 检查依赖关系:没有变化。

CodeSandbox

因此,发生的事情是可以理解的,但很难理解。

结论非常简单 - 相信 linter。这会让你省去不少头痛。

Mutable values like 'selectedResultTypeRef.current' aren't valid dependencies because mutating them doesn't re-render the component.

关于javascript - 第一个更新第二个依赖项的相关 useEffects 会导致奇怪的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73510367/

相关文章:

java - 如何使用 Spring Boot 在单个 html 页面中注入(inject)标签?

javascript - .each() 不启动点击处理程序

javascript - 如果数组循环内的项目不可用,则返回零并按工作日排列项目

reactjs - 如何在创建 react 应用程序中进行应用程序版本控制?

reactjs - 尝试打印具有数组的对象时出现错误

javascript - 在 href 中插入图像的最佳方法

java - java中读取XML的所有父节点

javascript - 在定义的特定时间从另一个 lambda 调用 aws lambda

javascript - 在 Babel 7+ 中使用 TypeScript 有什么好处

javascript - 有没有办法在 JavaScript 中实现 StringBuilder