![](/img/trans.png)
[英]State is not updated properly when using React.memo and useCallback
[英]State is not updated properly when using React effects
我正在開發一個小型 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 應用程序中,我有一些 state 包含待辦事項:
const [todos, setTodos] = useState([]);
const [flip, flipper] = useState(true);
const completeTodo = (todo) => {
todoService.completeTodo(todo);
flipper(!flip);
}
useEffect(() => {
setTodos(todoService.findAll());
}, [flip])
completeTodo
是一個 function ,我將它傳遞到我的Todo
組件中,以便在我想像這樣完成一個 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
時,它就會從服務 object 中刪除,並且 state 應該更新,但最奇怪的事情發生了。
當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"));
在這種情況下,您不需要調用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>
);
}
工作示例
https://codesandbox.io/s/cranky-hertz-sewc5?file=/src/App.js
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.