[英]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;
問: 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
中刪除,因此您可以看到差異
工作演示:
您評論並說失敗的代碼是由於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.