【发布时间】:2020-08-23 08:30:32
【问题描述】:
我正在开发一个小型 todo 应用程序作为使用 React 的练习。我有这样的模拟服务:
export default class TodoService {
constructor(todos) {
this.todos = new Map();
todos.forEach(todo => {
this.todos.set(todo.id, todo);
});
}
findAll() {
return Array.from(this.todos.values());
}
saveTodo(todo) {
this.todos[todo.id] = todo
}
completeTodo(todo) {
this.todos.delete(todo.id)
}
}
在我的 React 应用程序中,我有一些包含待办事项的状态:
const [todos, setTodos] = useState([]);
const [flip, flipper] = useState(true);
const completeTodo = (todo) => {
todoService.completeTodo(todo);
flipper(!flip);
}
useEffect(() => {
setTodos(todoService.findAll());
}, [flip])
completeTodo 是一个函数,我将它传递到我的Todo 组件中,以便在我想完成这样的待办事项时使用:
import React from "react";
const Todo = ({ todo, completeFn }) => {
return (
<form className="todo">
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
value=""
name={todo.id}
id={todo.id}
onClick={() => {
console.log(`completing todo...`)
completeFn(todo)
}} />
<label className="form-check-label" htmlFor={todo.id}>
{todo.description}
</label>
</div>
</form>
)
}
export default Todo
所以发生的情况是,每当用户点击复选框 completeFn 时,todo 就会被调用,它会从服务对象中删除并且状态应该更新,但最奇怪的事情发生了。
当TodoService.completeTodo() 被调用时,todo 被正确删除,但是当findAll() 被调用时,旧的todo 仍然存在!如果我将内容写入控制台,我可以看到该项目被删除,然后当我调用findAll 时以某种方式传送回来。为什么会这样?我是因为一些我不理解的 React 魔法?
编辑:更疯狂的是,如果我将其修改为仅对初始加载使用效果,如下所示:
const [todos, setTodos] = useState([]);
const completeTodo = (todo) => {
todoService.completeTodo(todo);
setTodos(todoService.findAll());
}
useEffect(() => {
setTodos(todoService.findAll());
}, [])
我得到一个超级怪异的结果:
谁能给我解释一下?
Edit2:这是一个完整的可重现示例(其中没有 index.html 和 <div id="root"></div>)。
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const Todo = ({ todo, completeFn }) => {
return (
<div>
<input
type="checkbox"
name={todo.id}
id={todo.id}
onClick={() => {
console.log(`completing todo...`)
completeFn(todo)
}} />
<label className="form-check-label" htmlFor={todo.id}>
{todo.description}
</label>
</div>
)
}
class TodoService {
constructor(todos) {
this.todos = new Map();
todos.forEach(todo => {
this.todos.set(todo.id, todo);
});
}
findAll() {
return Array.from(this.todos.values());
}
saveTodo(todo) {
this.todos[todo.id] = todo
}
completeTodo(todo) {
this.todos.delete(todo.id)
}
}
const App = () => {
let todoService = new TodoService([{
id: 1,
description: "Let's go home."
}, {
id: 2,
description: "Take down the trash"
}, {
id: 3,
description: "Play games"
}]);
const [todos, setTodos] = useState([]);
const [flip, flipper] = useState(true);
const completeTodo = (todo) => {
todoService.completeTodo(todo);
flipper(!flip);
}
useEffect(() => {
setTodos(todoService.findAll());
}, [flip])
return (
<div>
{todos.map(todo => <Todo key={todo.id} todo={todo} completeFn={completeTodo} />)}
</div>
)
};
ReactDOM.render(<App />, document.getElementById("root"));
【问题讨论】:
-
您是否可以创建一个最小的可重现示例
-
是的,我刚做了。
-
我认为这里的问题基本上是你试图创建一个存在于 React 渲染循环之外的状态对象。当
flipper被调用时,组件被重新渲染,并创建todoService的新实例。您刚刚更新的todoService属于上一个渲染周期。您需要找到某种方法来组合todo状态和todoService的功能,这就是自定义挂钩的用途。 -
我刚刚意识到我在
App中创建了模拟待办事项。我真蠢。但是有没有比我做的触发器更好的方法来强制状态更新? -
你也在
useEffectstackoverflow.com/questions/53715465/…中设置状态
标签: javascript reactjs react-hooks