簡體   English   中英

React:使用 useEffect 和/或 useState 鈎子混淆行為

[英]React: Confusing behavior with useEffect and/or useState hooks

在這里反應菜鳥。 我正在嘗試制作一個簡單的練習應用程序來幫助我學習 React,但我遇到了很多奇怪的行為。 這是一個有多個待辦事項列表可供選擇的待辦事項應用程序。 我要執行的行為是待辦事項列表的列表,您可以在其中找到 select 以及相應的待辦事項(如 wunderlist/msft 待辦事項)。 Select 一個不同的列表,它的待辦事項顯示等。此時它使用 static 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;

應用程序.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

問: useEffect - 我正在嘗試使用它來加載數據。 它一直抱怨缺少依賴項

A:你可以通過eslint (react-hooks/exhaustive-deps)忽略它,沒什么好擔心的


問: useState - 我用它來存儲數據。 它被初始化為一個空數組。 但是 render 在 useEffect 之前觸發,它說我的數據是未定義的,所以我的第二個列表永遠不會呈現。

A:一定要閱讀代碼中的注釋,希望能消除你的疑惑

// 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:這背后的原因是<React.StrictMode>

React 可能在提交之前多次調用渲染階段生命周期,或者它可能在根本不提交的情況下調用它們(因為錯誤或更高優先級的中斷)。

我已將它從演示中的index.js中刪除,因此您可以看到差異


工作演示:

編輯angerous-oskar-37rq9

您評論並說失敗的代碼是由於todoDetails.TodoList在您第一次渲染組件時未定義。 為避免這種情況,請使用一項初始化todoDetails或檢查todoDetails.TodoList是否在調用.map之前定義。

{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 function,您可以將其刪除並簡單地調用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 ,因為您不需要通過整個數組 go 來查找一項,從而使其更加昂貴。 另外,這樣,您無需訪問數組的第一項。

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

您將todoDetails用作object並將其初始化為array 如果您的todoDetails始終只有一個 todo,請將其初始化為 object,這樣您可以防止從數組訪問todoDetails.Title useEffect將始終在渲染后執行。

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

您必須將 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>
    ));
}

這是更新的演示

useEffect鈎子有時會很奇怪。 要修復它,您只需像這樣直接傳入您的getTodoData function :

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

useEffect(getTodoData, []);

在將getTodoData function 傳遞給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;

這篇 Stackoverflow 帖子與您的類似,可能會幫助您了解如何在將來使用useEffect掛鈎時避免錯誤。 我希望這有幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM