Creé un reductor que incrementa un UID global en cada envío con el increment
de acción. El valor inicial del UID es 0
.
Expectativa :
Esperaba que el UID se incrementara en 1 en cada envío.
realidad :
Los incrementos de UID por 2 y el estado registrado son inconsistentes con el valor del estado real.
Mi componente:
import React, { useReducer } from "react" let uid = 0 function nextUID() { uid = uid + 1 return uid } function reducer(state, action) { switch (action.type) { case "increment": const uid = nextUID() const newState = `current UID is ${uid}!` console.log(newState) return newState default: return state } } function TestComponent() { const [state, dispatch] = useReducer(reducer, "not clicked yet") return <button onClick={() => dispatch({ type: "increment" })}>{state}</button> } export default TestComponent
Salida :
número Clics | Etiqueta de botón | Salida de consola |
---|---|---|
0 | aún no se ha hecho clic | |
1 | ¡El UID actual es 1! | ¡El UID actual es 1! |
2 | ¡El UID actual es 3! | ¡El UID actual es 2! |
3 | ¡El UID actual es 5! | ¡El UID actual es 4! |
Pregunta :
¡Cómo es posible que la etiqueta del botón sea current UID is 3!
¡mientras que la salida de la consola para el cambio de estado era current UID is 2!
? ¿Es posible que react llame al reductor varias veces y descarte la salida de la consola la segunda vez?
Información adicional :
const uidRef = useRef(0)
en TestComponent
con un reductor anónimo que incrementa uidRef.current
. El comportamiento era el mismo.nextUID()
. El resultado fue new uid 1; new uid 2; new uid 4; ...
Gracias.
Muy bien , StrictMode te está molestando, pero en el buen sentido. El código que ha escrito no es deseable según React . StrictMode llama a su reducer
dos veces para eliminar cualquier efecto secundario . En su caso, la implementación de incrementar uid
es uno de esos efectos secundarios .
(Simplemente hacer console.log
no le mostraría que se está llamando a reducer dos veces ya que React lo silencia para la segunda llamada desde 17.0 . Así que he usado una función de log
para referirme al mismo console.log
);
Aquí está tu código :-
import React, { useReducer } from "react"; const log = console.log; let uid = 0; function nextUID() { uid = uid + 1; return uid; } function reducer(state, action) { switch (action.type) { case "increment": log("called reducer"); const uid = nextUID(); const newState = `current UID is ${uid}!`; console.log(newState); return newState; default: return state; } } function TestComponent() { const [state, dispatch] = useReducer(reducer, "not clicked yet"); return ( <button onClick={() => dispatch({ type: "increment" })}>{state}</button> ); } export default TestComponent;
Ahora su código original funcionaría bien en producción porque el reductor se llamaría solo una vez (ya que las producciones no tienen un componente de contenedor de modo estricto ), pero no es una buena práctica.
A continuación se muestra otra implementación en la que el estado no se muta directamente y, por lo tanto, entre dos llamadas al reductor, el estado anterior sigue siendo el mismo y el siguiente valor de incremento es consistente. Si mutaste el estado directamente como state.val += 1
, verías el mismo comportamiento que tu ejemplo en modo estricto .
import React, { useReducer } from "react"; const log = console.log; function reducer(state, action) { switch (action.type) { case "increment": log("called reducer"); const nextStateVal = state.val + 1; return { ...state, text: `current UID is ${nextStateVal}`, val: nextStateVal }; default: return state; } } function TestComponent() { const [state, dispatch] = useReducer(reducer, { text: "not clicked yet", val: 0 }); return ( <button onClick={() => dispatch({ type: "increment" })}> {state.text} </button> ); } export default TestComponent;