我正在学习 React,试图制作一个任务管理器应用程序。我试图用备忘录记住组件,但是这样做时状态表现得很奇怪,当通过单击 2 个不同任务的复选框完成任务时,其他 gif 附件未完成,以便您理解我的意思:
和 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 作为函数,但我不明白如果我没有多次修改状态或类似的东西,为什么我必须将它用作函数。
答案 0 :(得分:2)
问题在于 toggleDoneTask
何时被记住。当它被记忆时,它的内部引用状态值也会被记忆,对应一个过时的状态值。
一旦您点击 1,任务 2 和 3 将使用相同的先前道具,其中 toggleDoneTask
没有当前状态。
要解决这个问题,您需要重构 toggleDoneTask
。您需要将一个函数传递给您的 setState,而不是将状态更新为 setTaskItems(tasks)
。传递一个函数,setTaskItems(prevTasks => { // ... some logic; return nextask})
参数总是正确的。
鉴于所有这些,如果您像下面这样重构 prevTasks
,一切都会按预期工作:
toggleDoneTask