useReducer 调度被调用两次
useReducer dispatch being called twice
我有一个待办事项列表,其中 <li>
带有编辑按钮和切换按钮。
实际上我已经制作了一个减速器来执行以下操作:添加、删除、切换。
它几乎工作正常,但它执行了两次操作。
例如,toggle 函数将属性 done 设置为 true,然后函数再次执行,toggle 函数再次将属性设置为 false。
这是我在 github 中的项目:https://github.com/brenosantin96/TodoListWithUseReducer01
这是我做的 Reducer 代码:
function reducer(stateTodos: todoType[], actionType: actionType) {
switch (actionType.type) {
case 'ADD-TODO':
if (actionType.payload?.id && actionType.payload?.name) {
let newStateTodos = [...stateTodos];
newStateTodos.push({
id: actionType.payload?.id,
name: actionType.payload?.name,
done: actionType.payload?.done
})
console.log("Adicionado TODO");
return newStateTodos;
break;
}
case 'DEL-TODO':
if (actionType.payload?.id) {
let newStateTodos = [...stateTodos];
newStateTodos = newStateTodos.filter((item) => item.id !== actionType.payload?.id)
console.log("Deletado TODO");
return newStateTodos;
break;
}
case 'TOGGLE-TODO':
if (actionType.payload?.id) {
let newStateTodos = [...stateTodos];
for (let item of newStateTodos) {
if (item.id === actionType.payload?.id) {
item.done = !item.done
}
}
console.log(newStateTodos);
return newStateTodos;
}
default:
return stateTodos;
}
}
下面是函数组件:
function App2() {
const [inputTodo, setInputTodo] = useState('');
const [stateTodos, dispatch] = useReducer(reducer, initialToDos);
//controlling input always when value changes
const handleInputTodo = (e: ChangeEvent<HTMLInputElement>) => {
setInputTodo(e.target.value);
}
//Function that makes add todo.
const handleForm = (e: React.FormEvent) => {
e.preventDefault();
dispatch({
type: 'ADD-TODO', payload: {
id: parseInt(uuidv4()),
name: inputTodo,
done: false
}
});
setInputTodo('');
}
//Function that calls deleteTodo to delete a todo
const handleDelTodo = (id: number) => {
dispatch({
type: 'DEL-TODO', payload: {
id: id
}
});
}
//Funcao that calls Toggle-TODO, it should toggle the done to false or the false to true.
const handleToggleTodo = (id: number) => {
dispatch({
type: 'TOGGLE-TODO', payload: {
id
}
})
}
return (
<div>
<form action="submit" onSubmit={handleForm}>
<input type="text" value={inputTodo} onChange={handleInputTodo} />
<button type='submit'>ENVIAR</button>
</form>
<div>
LIST:
<ul>
{stateTodos.map((item) => {
return (
<li key={item.id}>
{item.id} - {item.name}
<button onClick={() => handleDelTodo(item.id)}> DELETE </button>
<button onClick={() => handleToggleTodo(item.id)}>CHECK</button>
</li>
)
})}
</ul>
</div>
</div>
)
}
知道我遗漏了什么吗?
它必须是该代码之外的东西。
我已经复制了文件并适应了 javascript (因为我的项目是在 JS 中)。
它工作得很好(动作只执行一次)。
/*
Question: useReducer dispatch being called twice
Url:
*/
import { v4 as uuidv4 } from 'uuid';
import { useReducer, useState } from 'react';
function reducer(stateTodos, actionType) {
switch (actionType.type) {
case 'ADD-TODO':
if (actionType.payload.id && actionType.payload.name) {
const newStateTodos = [...stateTodos];
newStateTodos.push({
id: actionType.payload.id,
name: actionType.payload.name,
done: actionType.payload.done,
});
console.log('Adicionado TODO');
return newStateTodos;
break;
}
case 'DEL-TODO':
if (actionType.payload.id) {
let newStateTodos = [...stateTodos];
newStateTodos = newStateTodos.filter((item) => item.id !== actionType.payload.id);
console.log('Deletado TODO');
return newStateTodos;
break;
}
case 'TOGGLE-TODO':
if (actionType.payload.id) {
const newStateTodos = [...stateTodos];
for (const item of newStateTodos) {
if (item.id === actionType.payload.id) {
item.done = !item.done;
}
}
console.log(newStateTodos);
return newStateTodos;
}
default:
return stateTodos;
}
}
const initialToDos = [];
function Question19() {
const [inputTodo, setInputTodo] = useState('');
const [stateTodos, dispatch] = useReducer(reducer, initialToDos);
// controlando o input para sempre que digittar atualizar o valor do proprio.
const handleInputTodo = (e) => {
setInputTodo(e.target.value);
};
// Funcao que faz acao de adicionar Todo ao dar submit no FORM
const handleForm = (e) => {
e.preventDefault();
dispatch({
type: 'ADD-TODO',
payload: {
id: parseInt(uuidv4()),
name: inputTodo,
done: false,
},
});
setInputTodo('');
};
// Funcao que faz acao de deletar TODO ao clicar no botao de delete
const handleDelTodo = (id) => {
dispatch({
type: 'DEL-TODO',
payload: {
id,
},
});
};
// Funcao que chama o Toggle-TODO, chama a action Toggle TODO e altera o status DONE para FALSE (nao esta funcionando ainda)
const handleToggleTodo = (id) => {
dispatch({
type: 'TOGGLE-TODO',
payload: {
id,
},
});
};
return (
<div>
<form action="submit" onSubmit={handleForm}>
<input type="text" value={inputTodo} onChange={handleInputTodo} />
<button type="submit">ENVIAR</button>
</form>
<div>
LISTA:
<ul>
{stateTodos.map((item) => (
<li key={item.id}>
{item.id} - {item.name}
<button onClick={() => handleDelTodo(item.id)}> DELETAR </button>
<button onClick={() => handleToggleTodo(item.id)}>CHECK</button>
</li>
))}
</ul>
</div>
</div>
);
}
export default Question19;
React 出于内部目的多次调用 reducer。当您使用 严格模式 时会发生这种情况。当您删除它时,您可以清楚地看到只有一次反应调用。这仅发生在开发模式下,如果您切换到生产模式,它不会调用两次或更多次。
您可以了解有关 useReducer dispatch calls reduce twice 的更多详细信息,那里已对其进行了简要讨论。
另一个问题是 toggleFunction 总是 returns false。这又是由于 React 调用它使 !false -> true 并在接下来调用它时改变 !true -> false。所以均匀调用总是 return false 而奇数调用总是使它成为真。
您可以使用纯函数来解决它。在 redux 应用程序中,你必须确保一切都是纯净的。在你的 reducer 中使用纯函数是最基本的。因此,每次您提供相同的输入时,您都会得到相同的输出。
case 'TOGGLE-TODO':
if (actionType.payload?.id) {
let newStateTodos = [...stateTodos];
for (let item of newStateTodos) {
if (item.id === actionType.payload?.id) {
item.done = !item.done
}
}
console.log(newStateTodos);
return [...newStateTodos];
}
这里你只传递了 id 并期望它根据自己的值改变状态,这不是一个好主意。除了 id,您还必须为 done
属性 传递新状态。所以无论你用相同的参数调用多少次,你总是会得到相同的输出。
您可以使用以下代码段更改切换功能
case 'TOGGLE-TODO':
const {id, done} = actionType.payload;
if (id) {
let newStateTodos = [...stateTodos];
newStateTodos.find(t => t.id === id).done = done;
return newStateTodos;
}
并更新调度调用。
const handleToggleTodo = (id) => {
dispatch({
type: 'TOGGLE-TODO', payload: {
id,
done: !stateTodos.find(t => t.id === id).done
}
})
}
如果为每个 to-do 列表项创建单独的组件,您可以改进这一点。并摆脱查找特定待办事项的完成状态。
我有一个待办事项列表,其中 <li>
带有编辑按钮和切换按钮。
实际上我已经制作了一个减速器来执行以下操作:添加、删除、切换。 它几乎工作正常,但它执行了两次操作。 例如,toggle 函数将属性 done 设置为 true,然后函数再次执行,toggle 函数再次将属性设置为 false。
这是我在 github 中的项目:https://github.com/brenosantin96/TodoListWithUseReducer01
这是我做的 Reducer 代码:
function reducer(stateTodos: todoType[], actionType: actionType) {
switch (actionType.type) {
case 'ADD-TODO':
if (actionType.payload?.id && actionType.payload?.name) {
let newStateTodos = [...stateTodos];
newStateTodos.push({
id: actionType.payload?.id,
name: actionType.payload?.name,
done: actionType.payload?.done
})
console.log("Adicionado TODO");
return newStateTodos;
break;
}
case 'DEL-TODO':
if (actionType.payload?.id) {
let newStateTodos = [...stateTodos];
newStateTodos = newStateTodos.filter((item) => item.id !== actionType.payload?.id)
console.log("Deletado TODO");
return newStateTodos;
break;
}
case 'TOGGLE-TODO':
if (actionType.payload?.id) {
let newStateTodos = [...stateTodos];
for (let item of newStateTodos) {
if (item.id === actionType.payload?.id) {
item.done = !item.done
}
}
console.log(newStateTodos);
return newStateTodos;
}
default:
return stateTodos;
}
}
下面是函数组件:
function App2() {
const [inputTodo, setInputTodo] = useState('');
const [stateTodos, dispatch] = useReducer(reducer, initialToDos);
//controlling input always when value changes
const handleInputTodo = (e: ChangeEvent<HTMLInputElement>) => {
setInputTodo(e.target.value);
}
//Function that makes add todo.
const handleForm = (e: React.FormEvent) => {
e.preventDefault();
dispatch({
type: 'ADD-TODO', payload: {
id: parseInt(uuidv4()),
name: inputTodo,
done: false
}
});
setInputTodo('');
}
//Function that calls deleteTodo to delete a todo
const handleDelTodo = (id: number) => {
dispatch({
type: 'DEL-TODO', payload: {
id: id
}
});
}
//Funcao that calls Toggle-TODO, it should toggle the done to false or the false to true.
const handleToggleTodo = (id: number) => {
dispatch({
type: 'TOGGLE-TODO', payload: {
id
}
})
}
return (
<div>
<form action="submit" onSubmit={handleForm}>
<input type="text" value={inputTodo} onChange={handleInputTodo} />
<button type='submit'>ENVIAR</button>
</form>
<div>
LIST:
<ul>
{stateTodos.map((item) => {
return (
<li key={item.id}>
{item.id} - {item.name}
<button onClick={() => handleDelTodo(item.id)}> DELETE </button>
<button onClick={() => handleToggleTodo(item.id)}>CHECK</button>
</li>
)
})}
</ul>
</div>
</div>
)
}
知道我遗漏了什么吗?
它必须是该代码之外的东西。 我已经复制了文件并适应了 javascript (因为我的项目是在 JS 中)。 它工作得很好(动作只执行一次)。
/*
Question: useReducer dispatch being called twice
Url:
*/
import { v4 as uuidv4 } from 'uuid';
import { useReducer, useState } from 'react';
function reducer(stateTodos, actionType) {
switch (actionType.type) {
case 'ADD-TODO':
if (actionType.payload.id && actionType.payload.name) {
const newStateTodos = [...stateTodos];
newStateTodos.push({
id: actionType.payload.id,
name: actionType.payload.name,
done: actionType.payload.done,
});
console.log('Adicionado TODO');
return newStateTodos;
break;
}
case 'DEL-TODO':
if (actionType.payload.id) {
let newStateTodos = [...stateTodos];
newStateTodos = newStateTodos.filter((item) => item.id !== actionType.payload.id);
console.log('Deletado TODO');
return newStateTodos;
break;
}
case 'TOGGLE-TODO':
if (actionType.payload.id) {
const newStateTodos = [...stateTodos];
for (const item of newStateTodos) {
if (item.id === actionType.payload.id) {
item.done = !item.done;
}
}
console.log(newStateTodos);
return newStateTodos;
}
default:
return stateTodos;
}
}
const initialToDos = [];
function Question19() {
const [inputTodo, setInputTodo] = useState('');
const [stateTodos, dispatch] = useReducer(reducer, initialToDos);
// controlando o input para sempre que digittar atualizar o valor do proprio.
const handleInputTodo = (e) => {
setInputTodo(e.target.value);
};
// Funcao que faz acao de adicionar Todo ao dar submit no FORM
const handleForm = (e) => {
e.preventDefault();
dispatch({
type: 'ADD-TODO',
payload: {
id: parseInt(uuidv4()),
name: inputTodo,
done: false,
},
});
setInputTodo('');
};
// Funcao que faz acao de deletar TODO ao clicar no botao de delete
const handleDelTodo = (id) => {
dispatch({
type: 'DEL-TODO',
payload: {
id,
},
});
};
// Funcao que chama o Toggle-TODO, chama a action Toggle TODO e altera o status DONE para FALSE (nao esta funcionando ainda)
const handleToggleTodo = (id) => {
dispatch({
type: 'TOGGLE-TODO',
payload: {
id,
},
});
};
return (
<div>
<form action="submit" onSubmit={handleForm}>
<input type="text" value={inputTodo} onChange={handleInputTodo} />
<button type="submit">ENVIAR</button>
</form>
<div>
LISTA:
<ul>
{stateTodos.map((item) => (
<li key={item.id}>
{item.id} - {item.name}
<button onClick={() => handleDelTodo(item.id)}> DELETAR </button>
<button onClick={() => handleToggleTodo(item.id)}>CHECK</button>
</li>
))}
</ul>
</div>
</div>
);
}
export default Question19;
React 出于内部目的多次调用 reducer。当您使用 严格模式 时会发生这种情况。当您删除它时,您可以清楚地看到只有一次反应调用。这仅发生在开发模式下,如果您切换到生产模式,它不会调用两次或更多次。 您可以了解有关 useReducer dispatch calls reduce twice 的更多详细信息,那里已对其进行了简要讨论。
另一个问题是 toggleFunction 总是 returns false。这又是由于 React 调用它使 !false -> true 并在接下来调用它时改变 !true -> false。所以均匀调用总是 return false 而奇数调用总是使它成为真。 您可以使用纯函数来解决它。在 redux 应用程序中,你必须确保一切都是纯净的。在你的 reducer 中使用纯函数是最基本的。因此,每次您提供相同的输入时,您都会得到相同的输出。
case 'TOGGLE-TODO':
if (actionType.payload?.id) {
let newStateTodos = [...stateTodos];
for (let item of newStateTodos) {
if (item.id === actionType.payload?.id) {
item.done = !item.done
}
}
console.log(newStateTodos);
return [...newStateTodos];
}
这里你只传递了 id 并期望它根据自己的值改变状态,这不是一个好主意。除了 id,您还必须为 done
属性 传递新状态。所以无论你用相同的参数调用多少次,你总是会得到相同的输出。
您可以使用以下代码段更改切换功能
case 'TOGGLE-TODO':
const {id, done} = actionType.payload;
if (id) {
let newStateTodos = [...stateTodos];
newStateTodos.find(t => t.id === id).done = done;
return newStateTodos;
}
并更新调度调用。
const handleToggleTodo = (id) => {
dispatch({
type: 'TOGGLE-TODO', payload: {
id,
done: !stateTodos.find(t => t.id === id).done
}
})
}
如果为每个 to-do 列表项创建单独的组件,您可以改进这一点。并摆脱查找特定待办事项的完成状态。