从先前状态版本到钩子
我试图操纵 api 响应,以便尽可能以更干净的方式呈现。
我有这个状态:
state = {
tasks: {
workingProgress: [],
toDo: [],
done: [],
},
};
从 api 我得到这样的对象:
{id: "1", task: "add button", team: "Metro", status: "workingProgress", …}
{id: "8", task: "fixed accessibility SR", team: "Metro", status: "toDo", …}
我的想法是将我的状态转换为这样的形式:
tasks: {
workingProgress: [{id: "1", task: "add button", team: "Metro", ...},{}, {}],
toDo: [{id: "8", task: "fixed accessibility SR", team: "Metro", ...}, {}, {}],
done: [],
},
我有这个并且可以很好地工作:
state = {
tasks: {
workingProgress: [],
toDo: [],
done: [],
},
};
componentDidMount() {
TasksAPI.getAll().then((res) => {
this.setState(({ tasks }) => {
res.forEach((item) => {
if (tasks[item.status]) {
tasks[item.status].push(item);
}
});
return { tasks };
});
});
}
我的问题是因为我打算从 钩子 更改“setState”代码。
const [tasks, setTasks] = useState({
workingProgress: [],
toDo: [],
done: [],
});
const history = useHistory();
useEffect(() => {
TasksAPI.getAll().then((res) => {
setTasks(({ tasks }) => {
res.forEach((item) => {
if (tasks[item.status]) {
tasks[item.status].push(item);
}
});
return { tasks };
});
});
}, []);
但是当我尝试这个时,我的代码中断了:
Tasks.js:21 Uncaught (in promise) TypeError: Cannot read property 'toDo' of undefined
这是我的回报:
<div>
{Object.entries(tasks).map(([key, status]) => (
<div key={key}>
<div >
<ol>
{status.map((item) => (
<li>
<div>
......
</div>
我的“钩子”方法不正确?
我认为问题出在您的
useEffect
代码中,特别是
setTasks(({ task }) =>...
和
return { task };
,它们有两个主要问题:
-
在此代码中,您创建了一个本地
tasks
变量,该变量覆盖了useState
返回的状态中的tasks
。 -
您还试图破坏此状态并获取
tasks
属性,但这是错误的,因为您的状态已经是任务。您从之前的实现中继承了此逻辑,其中您的state
是一个包含tasks
属性的对象。
您的 useEffect 应按如下方式更改:
useEffect(() => {
TasksAPI.getAll().then((res) => {
setTasks((previousTasks) => {
const newTasks = {...previousTasks}
res.forEach((item) => {
if (newTasks[item.status]) {
newTasks[item.status] = [...newTasks[item.status], item]
}
});
return newTasks;
});
});
}, []);
newTasks = [...previousTasks]
和
newTasks[item.status] = [...newTasks[item.status], item]
确保您不会改变先前的任务对象和状态数组并创建新的数组。
即使您的第一个解决方案(使用类)也没有解决这个问题,这可能会导致 React 更新出现错误。
但是,如果您不关心深度不​​变性,您可以将
forEach
中的代码简化为(虽然我不建议这样做):
if (newTasks[item.status]) {
newTasks[item.status].push(item);
}
问题
据我所知,您遇到了一个简单的状态突变问题。效果钩子回调对
res
数组进行 for-each 操作,并直接推送到
tasks
状态,而不是创建新的数组和状态引用。
您还试图从
tasks
状态解构
tasks
,这显然不是定义的属性。当它是
this.state.tasks
并且“previousState”是
this.state
时,您可以解构
tasks
,但现在在
useState
钩子中
tasks
是
状态!然后,您返回一个对象
return { task };
,该对象带有单个
tasks
属性,且实际状态嵌套得更深,例如使其成为
tasks.tasks.toDo
。
解决方案
无需循环并入队多个状态更新(这些更新必然需要从每个先前入队的更新中进行正确更新),只需循环并创建新的下一个状态对象并入队单个状态更新即可。
const [tasks, setTasks] = useState({
workingProgress: [],
toDo: [],
done: [],
});
useEffect(() => {
TasksAPI.getAll()
.then((res) => {
setTasks(tasks => res.reduce(
(tasks, task) => ({
...tasks, // <-- shallow copy the state
[task.status]: [
...(tasks[task.status] ?? []), // <-- shallow copy array
task // <-- append new task item
],
}),
tasks, // <-- start from the previous state
));
});
}, []);