为了账号安全,请及时绑定邮箱和手机立即绑定

React:使用 useEffect 和/或 useState 钩子混淆行为

React:使用 useEffect 和/或 useState 钩子混淆行为

繁星点点滴滴 2022-10-13 16:58:52
在这里反应菜鸟。我正在尝试制作一个简单的练习应用程序来帮助我学习 React,但我遇到了很多奇怪的行为。这是一个有多个待办事项列表可供选择的待办事项应用程序。我要的行为是一个待办事项列表的列表,您可以在其中选择一个和待办事项(如 wunderlist/msft 待办事项)。选择一个不同的列表,它的待办事项显示等。此时它使用静态 json,其中每个项目都有一个子数组。useEffect - 我正在尝试使用它来加载数据。它一直抱怨缺少依赖项。当我添加它们时,它也会抱怨这一点。如果我对第二个参数使用空数组并且似乎多次触发,它会抱怨。useState - 我用它来存储数据。它被初始化为一个空数组。但是 render 在 useEffect 之前触发,它说我的数据是未定义的,所以我的第二个列表永远不会呈现。我在代码中有几个console.logs,它们都被多次触发。我敢肯定这只是菜鸟的错误,但在这一点上我很困惑。这是我的代码:数据/Todo.jsconst 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;
查看完整描述

4 回答

?
梦里花落0921

TA贡献1772条经验 获得超5个赞

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


答:你可以忽略它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演示中删除了它,所以你可以看到差异


查看完整回答
反对 回复 2022-10-13
?
牛魔王的故事

TA贡献1830条经验 获得超3个赞

您评论并说失败的代码是由于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函数,您可以删除它并简单地调用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);

};

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


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


查看完整回答
反对 回复 2022-10-13
?
慕桂英546537

TA贡献1848条经验 获得超10个赞

你必须把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>

    ));

}

这是更新的演示


查看完整回答
反对 回复 2022-10-13
?
千巷猫影

TA贡献1829条经验 获得超7个赞

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;


查看完整回答
反对 回复 2022-10-13
  • 4 回答
  • 0 关注
  • 84 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信