【问题标题】:Cannot update a component (`TodoForm`) while rendering a different component (`TodoTask`). [SOLUTION] [React Redux To-Do App]在渲染不同的组件 (`TodoTask`) 时无法更新组件 (`TodoForm`)。 [解决方案] [React Redux 待办事项应用]
【发布时间】:2022-10-07 03:07:44
【问题描述】:

在写这篇文章时,我意识到解决方案是什么

每次我将任务分派到我的商店时,都会发生以下错误:

我知道为什么会这样。当我尝试使用 useSelector 获取待办事项列表然后映射到列表时,就会发生这种情况。但是,映射不是问题,而是在 map 函数上返回一个反应组件。如果我不返回功能组件而是使用 HTML,它就可以正常工作。因此,从我的 POV 来看,问题是返回一个反应功能组件,同时在地图功能上将道具传递给它。

这是我的家庭组件的代码:

    import Input from \'../components/Input\';
    import TodoForm from \'../components/TodoForm\';
    
    
    function Home() {
      document.title = \"MyTodo | Home\"
    
      return (
          <div className=\"App\">
              <h1>MyTodo</h1>
              <Input />
              <TodoForm />
          </div>
      );
    }
    
    export default Home;

在按键按下时分派操作的输入组件:

import {useState} from \'react\'
import { useDispatch } from \'react-redux\';
import { todoActions } from \'../store/todo\';

const Input = () => {
    const [inputText, setInputText] = useState(\"\");
    const dispatch = useDispatch();

    const handleChange = (e) => setInputText(e.target.value)

    const handleKeyPress = (event) => {
        if (event.code === \"Enter\") {
            // if the expression is false, that means the string has a length of 0 after stripping white spaces
            const onlyWhiteSpaces = !inputText.replace(/\\s/g, \"\").length;

            !onlyWhiteSpaces &&
                dispatch(
                    todoActions.addTask({ label: inputText, done: false })
                );
            setInputText(\"\");
        }
    };

  return (
      <input
          type=\"text\"
          onKeyDown={(e) => handleKeyPress(e)}
          onChange={(e) => handleChange(e)}
          value={inputText}
      />
  );
}

export default Input

我使用useSelector 的 TodoForm 从 redux 存储中获取待办事项列表并通过它进行映射:

import { useSelector } from \"react-redux\";
import { v4 as uuidv4 } from \"uuid\";
import TodoTask from \"./TodoTask\";


const TodoForm = () => {
    const tasks = useSelector((state) => state.todo.taskList);

     const renderedListItems = tasks.map((task, index) => {
         return (
             <TodoTask
                 key={uuidv4()}
                 task={task}
                 targetIndex={index}
             />
         );
     });

     return <div className=\"container\">{renderedListItems}</div>;
};

export default TodoForm;

最后是 TodoTask 组件,它是上面 map 函数中返回的子组件:

import { useDispatch } from \"react-redux\";
import { todoActions } from \"../store/todo\";

const TodoTask = ({ task, targetIndex }) => {
    const {text, done} = task;

    console.log(\"Task: \", task);

    const dispatch = useDispatch()
    const removeTask = dispatch(todoActions.deleteTask(targetIndex))

    return (
        <div
            className=\"alert alert-primary d-flex justify-content-between\"
            role=\"alert\"
        >
            {text}
            <button type=\"button\" className=\"btn-close\" onClick={()=>removeTask}></button>
        </div>
    );
};

export default TodoTask;

这是我第一次遇到这个问题,我知道这与 redux 以及 useSelector hook 如何强制组件重新渲染有关。因此 useSelector 正在重新渲染 TodoForm 组件,并且由于我们正在映射并返回另一个组件,因此该组件也同时被渲染。至少,我是这么理解的。让我知道我是否错了。

我尝试过的事情:

  1. 在 React.memo 中包装 TodoTask。在某处看到它作为此类问题的可能解决方案,但这不起作用。
  2. shallowEqual 作为TodoForm useSelector 的第二个参数传递。这确实可以防止页面进入无限循环,但任务显示为空但正在添加到 redux 存储。但是,使用这种方法,第一个警告仍然出现,并且TodoTask 组件中的控制台日志不执行。

    标签: reactjs redux react-hooks react-redux


    【解决方案1】:
    1. 将 shallowEqual 作为 TodoForm useSelector 的第二个参数传递。这确实可以防止页面进入无限循环,但任务显示为空但正在添加到 redux 存储。但是,使用这种方法,第一个警告仍然出现,并且 TodoTask 组件中的控制台日志不执行。

    在写这部分时,我意识到我做错了什么。 TodoTask 组件中的控制台日志正在工作,但我让浏览器控制台仅过滤错误。当我检查消息部分时,我看到一切正常。然后当我检查任务组件时,我注意到我试图读取一个不存在的属性,因此为什么任务没有文本。

    换句话说,解决方案是在我的 TodoForm 组件中添加 shallowEqual 作为 useSelector 钩子的第二个参数,这是通过 todo 任务数组的一个映射。正如我所说,useSelector 强制组件重新渲染。 shallowEquals 检查现有状态是否与我们已有的状态不同,并避免不必要的重新渲染,这可能导致我的应用程序超过最大更新长度。

    代码修复[解决方案]:

    import { memo } from "react";
    import { shallowEqual, useSelector } from "react-redux";
    import { v4 as uuidv4 } from "uuid";
    import TodoTask from "./TodoTask";
    
    
    const TodoForm = () => {
        // shallowEqual prevents unnecessary re-renders which can lead to an infinite loop
        // it compares the current state with the previous one, if they are the same, it does not re-render the component
        const tasks = useSelector((state) => state.todo.taskList, shallowEqual);
    
         const renderedListItems = tasks.map((task, index) => {
             return (
                 <TodoTask
                     key={uuidv4()}
                     task={task}
                     targetIndex={index}
                 />
             );
         });
    
         return <div className="container">{renderedListItems}</div>;
    };
    
    export default memo(TodoForm);
    

    老实说,从昨天开始我就一直坚持这一点,我不敢相信当我正要寻求帮助时我就意识到了解决方案。希望这对将来面临类似问题的其他人有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-25
      • 1970-01-01
      • 2021-08-06
      • 1970-01-01
      • 2018-06-15
      • 1970-01-01
      • 2021-03-29
      相关资源
      最近更新 更多