【问题标题】:React: Confusing behavior with useEffect and/or useState hooksReact:使用 useEffect 和/或 useState 钩子混淆行为
【发布时间】:2020-10-11 03:40:33
【问题描述】:

在这里反应菜鸟。我正在尝试制作一个简单的练习应用程序来帮助我学习 React,但我遇到了很多奇怪的行为。这是一个有多个待办事项列表可供选择的待办事项应用程序。我要的行为是一个待办事项列表的列表,您可以在其中选择一个和待办事项(如 wunderlist/msft 待办事项)。选择一个不同的列表,它的待办事项显示等。此时它使用静态 json,其中每个项目都有一个子数组。

useEffect - 我正在尝试使用它来加载数据。它一直抱怨缺少依赖项。当我添加它们时,它也会抱怨这一点。如果我使用空数组作为第二个参数,它会抱怨并且似乎会触发多次。

useState - 我用它来存储数据。它被初始化为一个空数组。但是 render 在 useEffect 之前触发,它说我的数据未定义,所以我的第二个列表永远不会呈现。

我在代码中有几个 console.logs,它们都被多次触发。

我确定这只是菜鸟的错误,但在这一点上我很困惑。这是我的代码:

数据/Todo.js

const TodoData = [
  {
    Id: 1,
    Title: "Groceries",
    TodoList: [
      {
        Id: 1,
        Title: "Apples"
      },
      {
        Id: 2,
        Title: "Oranges"
      },
      {
        Id: 3,
        Title: "Bananas"
      }
    ]
  },
  {
    Id: 2,
    Title: "Daily Tasks",
    TodoList: [
      {
        Id: 11,
        Title: "Clean Kitchen"
      },
      {
        Id: 12,
        Title: "Feed Pets"
      },
      {
        Id: 13,
        Title: "Do Stuff"
      }
    ]
  },
  {
    Id: 3,
    Title: "Hardware Store",
    TodoList: []
  },
  {
    Id: 4,
    Title: "Costco",
    TodoList: [
      {
        Id: 21,
        Title: "Diapers"
      },
      {
        Id: 22,
        Title: "Cat Food"
      },
      {
        Id: 23,
        Title: "Apples"
      },
      {
        Id: 24,
        Title: "Bananas"
      }
    ]
  },
  {
    Id: 5,
    Title: "Work",
    TodoList: [
      {
        Id: 34,
        Title: "TPS Reports"
      }
    ]
  }
];

export default TodoData;

App.Js

import React, { useEffect, useState } from "react";
import TodoData from "./data/Todo.js";

function App() {
  const [todoData, setTodoData] = useState([]);
  const [todoDetails, setTodoDetails] = useState([]);

  useEffect(() => {
    if (todoData.length === 0) {
      getTodoData();
    }
  }, [todoData]);

  const getTodoData = () => {
    setTodoData(TodoData);
    console.log("getting todo data");
    getTodoDetails(1);
  };

  const getTodoDetails = id => {
    const result = TodoData.filter(x => x.Id === id);
    console.log(result[0]);
    setTodoDetails(result[0]);
  };

  const handleClick = e => {
    const selectedId = Number(e.target.getAttribute("data-id"));
    getTodoDetails(selectedId);
  };

  return (
    <div className="App">
      <div className="container-fluid">
        <div className="row">
          <div className="list-group col-md-4 offset-md-1">
            {todoData.map(todos => (
              <button
                key={todos.Id}
                data-id={todos.Id}
                className="btn list-group-item d-flex justify-content-between align-items-center"
                onClick={handleClick}
              >
                {todos.Title}
                <span className="badge badge-primary badge-pill">
                  {todos.TodoList.length}
                </span>
              </button>
            ))}
          </div>
          <div className="col-md-6">
            <ul className="list-group">
              <h2>{todoDetails.Title} List</h2>

              {console.log(todoDetails.TodoList)}

              {/* this fails miserably */}
              {/* {todoDetails.TodoList.map(details => (
                <li className="list-group-item" key={details.Id}>
                  {details.Title}
                </li>
              ))} */}

              <li className="list-group-item">Cras justo odio</li>
              <li className="list-group-item">Dapibus ac facilisis in</li>
              <li className="list-group-item">Morbi leo risus</li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
演示:https://codesandbox.io/s/interesting-dan-rdoyh?fontsize=14&hidenavigation=1&theme=dark

【问题讨论】:

  • 请将代码的相关部分添加到问题中。如果外部链接被删除或修改,问题将没有未来价值

标签: javascript reactjs


【解决方案1】:

问: useEffect - 我正在尝试使用它来加载数据。它一直抱怨缺少依赖项

A:你可以通过eslint (react-hooks/exhaustive-deps)忽略它,不用担心


问: useState - 我用它来存储数据。它被初始化为一个空数组。但是 render 在 useEffect 之前触发,它说我的数据未定义,所以我的第二个列表永远不会呈现。

答:请阅读代码中的 cmets,希望能消除您的疑虑

// you are intializing `todoDetails` with array, when there will be object
// hence the error while mapping inside the html/jsx
// const [todoDetails, setTodoDetails] = useState([]);

// init with `null`, why?
const [todoDetails, setTodoDetails] = useState(null);

// so that you can do something like this
{
  todoDetails && (   // <---------- HERE
    <ul className="list-group">
      <h2>{todoDetails.Title} List</h2>

      {console.log(todoDetails.TodoList)}

      {/* this fails miserably */}
      {todoDetails.TodoList.map(details => (
        <li className="list-group-item" key={details.Id}>
          {details.Title}
        </li>
      ))}
    </ul>
  )
}

问:现在你收到多个console.log

A:这背后的原因是&lt;React.StrictMode&gt;

React 可能在提交之前多次调用渲染阶段生命周期,或者它可能在根本不提交的情况下调用它们(因为错误或更高优先级的中断)。

我已经从演示中的index.js 中删除了它,所以你可以看到差异


工作演示:

【讨论】:

    【解决方案2】:

    您评论并说失败的代码是由于todoDetails.TodoList 在您第一次渲染组件时未定义。为避免这种情况,请使用一项初始化 todoDetails,或者在调用 .map 之前检查是否定义了 todoDetails.TodoList

    {todoDetails.TodoList ? todoDetails.TodoList.map(details => (
      <li className="list-group-item" key={details.Id}>
        {details.Title}
      </li>
    )) : null}
    

    const [todoDetails, setTodoDetails] = useState(TodoData[0]);
    

    不要太担心eslint (react-hooks/exhaustive-deps) 错误。您需要了解useEffect 的第二个参数的含义。在您的代码中,您将todoData 传递给useEffect

      useEffect(() => {
        if (todoData.length === 0) {
          getTodoData();
        }
      }, [todoData]);
    

    这意味着,每当todoData 更改时,useEffect 就会运行,其行为类似于componentDidUpdate。如果你只传递一个空数组,这意味着你的 useEffect 没有依赖关系,它只会被执行一次,表现得像一个componentDidMount。所以,你在这里做的很好。

    除此之外,我可以为您提供一些关于您的代码的提示:

    关于您的 handleClick 函数,您可以删除它并简单地调用 getTodoDetails(id) 传递 id 而不是将其添加到 data-id 并稍后获取它,这样:

    <button
      key={todos.Id}
      className="btn list-group-item d-flex justify-content-between align-items-center"
      onClick={() => getTodoDetails(todos.Id)} // <---- change is in here
    >
      {todos.Title}
      <span className="badge badge-primary badge-pill">
        {todos.TodoList.length}
      </span>
    </button>
    

    在您的getTodoDetails 上,您可以将filter 更改为find,因为您无需遍历整个数组即可找到一项,从而使其更昂贵。此外,您无需访问数组的第一项。

    const getTodoDetails = id => {
      const result = TodoData.find(x => x.Id === id);
      console.log(result);
      setTodoDetails(result);
    };
    

    您将todoDetails 用作object 并将其初始化为array。如果您的todoDetails 总是只有一个待办事项,请将其初始化为一个对象,这样您就可以防止从数组中访问todoDetails.TitleuseEffect 将始终在渲染后执行。

    const [todoDetails, setTodoDetails] = useState({});
    

    【讨论】:

      【解决方案3】:

      useEffect 钩子有时会很奇怪。要修复它,您只需像这样直接传递您的 getTodoData 函数:

       const getTodoData = () => {
        setTodoData(TodoData);
        console.log("getting todo data");
        getTodoDetails(1);
      };
      
      useEffect(getTodoData, []);
      

      在将getTodoData 函数传递给useEffect 之前定义它很重要,否则会出错。

      我取消了您“惨遭失败”的代码的注释,并且能够让一切工作完美无缺。

      这是您的固定App.js 文件的全部内容:

      import React, { useEffect, useState } from "react";
      import TodoData from "./data/Todo.js";
      
      function App() {
        const [todoData, setTodoData] = useState([]);
        const [todoDetails, setTodoDetails] = useState([]);
      
        const getTodoData = () => {
          setTodoData(TodoData);
          console.log("getting todo data");
          getTodoDetails(1);
        };
      
        useEffect(getTodoData, []);
      
        const getTodoDetails = id => {
          const result = TodoData.filter(x => x.Id === id);
          console.log(result[0]);
          setTodoDetails(result[0]);
        };
      
        const handleClick = e => {
          const selectedId = Number(e.target.getAttribute("data-id"));
          getTodoDetails(selectedId);
        };
      
        return (
          <div className="App">
            <div className="container-fluid">
              <div className="row">
                <div className="list-group col-md-4 offset-md-1">
                  {todoData.map(todos => (
                    <button
                      key={todos.Id}
                      data-id={todos.Id}
                      className="btn list-group-item d-flex justify-content-between align-items-center"
                      onClick={handleClick}
                    >
                      {todos.Title}
                      <span className="badge badge-primary badge-pill">
                        {todos.TodoList.length}
                      </span>
                    </button>
                  ))}
                </div>
                <div className="col-md-6">
                  <ul className="list-group">
                    <h2>{todoDetails.Title} List</h2>
      
                    {console.log(todoDetails.TodoList)}
      
                    {todoDetails.TodoList.map(details => (
                      <li className="list-group-item" key={details.Id}>
                        {details.Title}
                      </li>
                    ))}
                  </ul>
                </div>
              </div>
             </div>
          </div>
         );
      }
      export default App;
      

      This Stackoverflow post 与您的相似,可能会帮助您了解在将来使用 useEffect 挂钩时如何避免错误。我希望这会有所帮助!

      【讨论】:

        【解决方案4】:

        你得把getTodoData的逻辑放到useEffect钩子里

        useEffect(() => {
            const getTodoData = () => {
              setTodoData(TodoData);
              console.log("getting todo data");
              getTodoDetails(1);
            };
        
            if (todoData.length === 0) {
              getTodoData();
            }
          }, [todoData.length]);
        

        在渲染中

        {
          // check todoDetails has TodoList
          todoDetails.TodoList &&
            todoDetails.TodoList.map((details) => (
              <li className="list-group-item" key={details.Id}>
                {details.Title}
              </li>
            ));
        }
        

        这是更新后的demo

        【讨论】:

        • 虽然没问题,但请注意 getTodoData 依赖于 getTodoDetails,因此 getTodoDetails 也应该移入或指定为 deps(在这种情况下需要 useCallback)
        猜你喜欢
        • 2022-01-12
        • 2020-02-17
        • 2020-06-22
        • 1970-01-01
        • 2020-02-19
        • 1970-01-01
        • 1970-01-01
        • 2021-02-07
        • 1970-01-01
        相关资源
        最近更新 更多