setState 如何在反应中与备忘录一起使用

时间:2020-12-19 01:35:55

标签: javascript reactjs

我正在学习 React,试图制作一个任务管理器应用程序。我试图用备忘录记住组件,但是这样做时状态表现得很奇怪,当通过单击 2 个不同任务的复选框完成任务时,其他 gif 附件未完成,以便您理解我的意思: Image from Gyazo

我留下一个包含完整代码的代码框: Edit react-app-manger

和 git 存储库:https://github.com/FrancoRodao/learning-react/tree/master/src

任务组件:

import React, { useState, useEffect } from 'react'
import TaskRow from "../TaskRow";

const initialState = [{"id":1,"title":"1","description":"","done":false},{"id":2,"title":"2","description":"","done":false},{"id":3,"title":"3","description":"","done":true}]

function Tasks(props) {

    const [taskItems, setTaskItems] = useState(initialState)

    useEffect(() => {
        if (!props.newTask) return
        newTask({ id: taskItems.length + 1, ...props.newTask })
    }, [props.newTask])


    const newTask = (task) => {
        updateItems([...taskItems, task].map((task) => ({ ...task })))
    }

    const toggleDoneTask = (id) => {
        let taskItemsCopy = [...taskItems].map((task) => ({ ...task }))
        let newItems = taskItemsCopy.map((t) => {
            if (t.id === id) {
                t.done = !t.done
            };
            return t;
        })
        updateItems(newItems)
    }

    const updateItems = (tasks) => {
        localStorage.setItem('tasks', JSON.stringify(tasks))
        setTaskItems(tasks)
    }

    return (
        <React.Fragment>
            <h1>learning react </h1>
            <table>
                <thead>
                    <tr>
                        <th>Title</th>
                        <th>Description</th>
                        <th>Done</th>
                    </tr>
                </thead>
                <tbody>
                    {

                        props.show ? taskItems.map((task, i) =>
                            <TaskRow
                                task={task}
                                key={task.id}
                                toggleDoneTask={toggleDoneTask}>
                            </TaskRow>)
                            :

                            taskItems.filter((task) => !task.done)
                                .map((task) =>
                                    <TaskRow
                                        show={props.show}
                                        task={task}
                                        key={task.id}
                                        toggleDoneTask={toggleDoneTask}></TaskRow>
                                )
                    }
                </tbody>
            </table>
        </React.Fragment>
    )
}


export default Tasks

Task Row(任务项)组件

import React, { memo } from 'react'
import styled from "styled-components"

const Tr = styled.tr`
    display: block;
    /* height: ${props => props.show && props.taskDone ? '100%' : '0px'};
    opacity: ${props => props.show && props.taskDone ? '1' : '0'};
    visibility: ${props => props.show && props.taskDone ? 'visible' : 'hidden'};
    transition: 0.2s; */
`;

function TaskRow(props) {

    return (<React.Fragment>
        {console.log('render', props.task)}
        <Tr show={props.show} taskDone={props.task.done}>
            <td>
                {props.task.title}
            </td>
            <td>
                {props.task.description}
            </td>
            <td>
                <input type="checkbox"
                    checked={props.task.done}
                    onChange={() => props.toggleDoneTask(props.task.id)}
                />

            </td>
        </Tr>
    </React.Fragment>)

}


export default memo(TaskRow, (prev, next) => {
    const prevTaskKeys = Object.keys(prev.task);
    const nextTaskKeys = Object.keys(next.task);
  
    const sameLength = prevTaskKeys.length === nextTaskKeys.length;
    const sameEntries = prevTaskKeys.every(key => {
      return nextTaskKeys.includes(key) && prev.task[key] === next.task[key];
    });
    
    return sameLength && sameEntries;
})

我在另一个问题中找到了一个解决方案,使用 useReduce 和 setTaskItems 作为函数,但我不明白如果我没有多次修改状态或类似的东西,为什么我必须将它用作函数。

1 个答案:

答案 0 :(得分:2)

问题在于 toggleDoneTask 何时被记住。当它被记忆时,它的内部引用状态值也会被记忆,对应一个过时的状态值。

一旦您点击 1,任务 2 和 3 将使用相同的先前道具,其中 toggleDoneTask 没有当前状态。

要解决这个问题,您需要重构 toggleDoneTask。您需要将一个函数传递给您的 setState,而不是将状态更新为 setTaskItems(tasks)。传递一个函数,setTaskItems(prevTasks => { // ... some logic; return nextask}) 参数总是正确的。

鉴于所有这些,如果您像下面这样重构 prevTasks,一切都会按预期工作:

toggleDoneTask