javascript - 使用 React 效果时状态未正确更新
问题描述
我正在开发一个小型 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
带有 a <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"));
解决方案
useEffect
在这种情况下您不需要调用。您已经在useEffect
其中添加了一个依赖项,可以使用它来停止无限循环。但这里没有必要。你真的什么都没做fetch
您可以将代码更新为这样。
import React, { useState, useCallback, useEffect } from "react";
const Todo = ({ todo, completeFn }) => {
const handleOnclick = useCallback(() => {
// useCallback since function is passed down from parent
console.log(`completing todo...`);
completeFn(todo);
}, [completeFn, todo]);
return (
<div>
<input
type="checkbox"
name={todo.id}
id={todo.id}
onClick={handleOnclick}
/>
<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() {
console.log(Array.from(this.todos.values()));
return Array.from(this.todos.values());
}
saveTodo(todo) {
this.todos[todo.id] = todo;
}
completeTodo(todo) {
this.todos.delete(todo.id);
}
}
const todoService = new TodoService([
{
id: 1,
description: "Let's go home."
},
{
id: 2,
description: "Take down the trash"
},
{
id: 3,
description: "Play games"
}
]);
export default function App() {
const [todos, setTodos] = useState([]); // Set initial state
const completeTodo = todo => {
todoService.completeTodo(todo);
setTodos(todoService.findAll()); // Update state
};
useEffect(() => {
setTodos(todoService.findAll());
}, []); // Get and update from service on first render only
return (
<div>
{todos.map(todo => (
<Todo key={todo.id} todo={todo} completeFn={completeTodo} />
))}
</div>
);
}
工作示例
推荐阅读
- c# - Unity滚动条不能跨级别工作
- r - 如何调整绘图大小以便在 R Markdown 中使用?
- apache - 用于基本 URL 的 htaccess RewriteRule
- json - 来自 jq 尝试使用变量修改 JSON 的“无效数字文字”错误
- java - 如何等待反射线程完成
- javascript - 使用 react-router v4 添加或更新查询参数,无需重新渲染主要组件
- python - 回归模型 statsmodel python
- php - 在 wordpress 上操作 slug 和 url
- scala - 在 Spark 2+ 中通过 SparkSession 向 Kryo 注册类
- c# - 将 datagridview 值转换为 int