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 列表项创建单独的组件,您可以改进这一点。并摆脱查找特定待办事项的完成状态。