Hablando sobre la trampa de cierre y la solución de los componentes de la función de ganchos React

Creo que los amigos que usan ganchos de React para desarrollar deben haberse encontrado con algunos escenarios "extraños" de "trampa de cierre" más o menos (de hecho, es lógico). como sigue:

const testDemo = () => {
    const [count, setCount] = useState(1)
    useEffect(() => {
        setInterval(() => {
           console.log(count)
        }, 1000)
        //闭包陷阱
    }, [])
    const handleClick = () => {
        setCount(count+1)
    }

    return (
        <div onClick={handleClick}>
            click to add, count: {count}
        </div>
    )
    
}

Esperamos que después de hacer clic, el recuento impreso también se pueda actualizar, pero el hecho es que el recuento impreso no cambia cada vez.

¿Qué es una trampa de cierre?

En pocas palabras, los ganchos de reacción mantienen una lista enlazada cuando se renderizan para registrar la posición y el valor de useState y useEffect (esta es también la razón por la que state no puede usar if else, porque puede causar que el orden de estado useEffect en la lista enlazada cambie). estar desordenado, por lo que no se puede obtener el valor correcto)

Cada vez que se actualiza el estado, la lista vinculada se vuelve a representar desde el principio, pero dado que useEffect en el ejemplo anterior no depende de ningún estado, solo se activará en la primera representación y la función de devolución de llamada en useEffect no lo hará. se activará cuando se actualice la representación de setCount Por lo tanto, el recuento en el interior de setInterval sigue siendo el valor en el momento de la inicialización,
y no se ha obtenido el último. Esta es la trampa de cierre
 

razón:

En los componentes de función, si usamos el valor creado por useState en la función de devolución de llamada, se generará el cierre. El cierre se genera cuando se crea el componente de la función y almacenará en caché el valor del estado en el momento de la creación.

Si la función en el gancho está escrita como useEffect(()=>(),[]), es decir, solo se ejecuta durante la fase de montaje del componente , entonces el valor obtenido por la función en él es siempre solo el valor en el momento de la inicialización, incluso si modifica el valor en otro lugar, no podrá obtener el valor más reciente.

solución:

1) El más simple: useRef

Debido a que useRef obtiene el objeto en sí mismo cada vez, que son los datos del mismo espacio de memoria, por lo que se puede obtener el valor más reciente.

De la misma manera, si hacemos una copia superficial como esta, también podemos obtener el último valor (como el método Object.assign)

Principio: Utilizar la referencia del objeto para obtener directamente los datos del propio objeto

2) Clear-Rebuild: use useEffect para borrar el cierre generado cuando se actualiza la página

Dado que la función useEffect siempre se usa cuando se actualizan otros componentes, en el modo de actualización, se puede hacer limpiando y reconstruyendo

const [count, setCount] = useState(0)
    let myInterval = null

    const interval = () => {
        myInterval = setInterval(() => {
            console.log(count)
            if (count > 5) {
                clearInterval(myInterval)
            }
            setCount(count + 1)
        }, 1000)
    }
    useEffect(() => {
        //由于更新时清除了, 所以要重新模拟一下点击时的操作, 确保继续运行
        if (count > 0) {
            interval()
        }
        //更新时清除掉interval
        return () => clearInterval(myInterval)
    })
    return <div onClick={interval}>click count add : {count}</div>

No puede establecer el parámetro, luego se llamará a useEffect cada vez que se actualice, o puede establecer la dependencia del segundo parámetro de useEffect, que es un parámetro de tipo matriz, y se pasará la dependencia. Después del valor de la dependencia cambios, se volverá a ejecutar useEffect, obtenga el valor más reciente.
Si hay un temporizador escrito en él, es mejor volver a una función para borrar el temporizador. Si addEventListener escucha el evento, devuelve una función para borrar el evento de escucha.

3) Si es solo la operación de cálculo de variables, utilice el método de escritura de la función useState al actualizar

// 异常的写法
import { useState } from 'react';
 
export default function Counter() {
  const [count, setCount] = useState(0)
 
  function handle() {
    setCount(count + 1)
    // 当 setTimeout 执行时,
    // 注意:回调函数的 count 值不是 1,而是 0
    setTimeout(() => {
      setCount(count + 2)
    }, 0)
  }
 
  return (
    <div>
      <div>{count}</div>
      <button onClick={handle}>递增</button>
    </div>
  )
}


// 正常的写法
const [count, setCount] = useState(0);

const handle = () => {
    setCount(count + 1);
    setTimeout(() => {
       -  setCount(count + 2)
       +  setCount(count => count + 2) // 异步写法
   }, 0)
}

4) Defina una variable directamente para su uso, por ejemplo, defina la variable de identificación del temporizador fuera del componente de función (en el entorno global)

Para otro ejemplo, si no usa useState para establecer el valor (simplemente defina una variable let count = 1; ), use directamente count = count + 1; para modificar el valor. Si no usa setCount para modificar el valor, obtendrá el último conteo cada vez, porque el componente no se actualizará si no usa el componente de la función setCount. Siempre es el mismo cierre, por lo que el mismo valor se toma.

5) Si usa ganchos como useMemo o useCallback para formar un cierre, también puede agregar todas las dependencias a la matriz para lograr el efecto de actualización

function App() {
  return <Demo1 />
}
 
function Demo1(){
  const [num1, setNum1] = useState(1)
  const [num2, setNum2] = useState(10)
 
  const text = useMemo(()=>{
    return `num1: ${num1} | num2:${num2}`
  }, [num2])  // 此处只添加num2即可,因为num2和num1是存在于同一个闭包里,所以更新时是同步的
 
  function handClick(){
    setNum1(2)
    setNum2(20)
  }
 
  return (
    <div>
      {text}
      <div><button onClick={handClick}>click!</button></div>
    </div>
  )
}

Nota: Y si varias dependencias tienen la característica de cambiar al mismo tiempo, solo coloque una de ellas en la matriz
 

Artículo de referencia:

Referencia 1 : versión simplificada

Fuente 2 : versión detallada

Ejemplo 3 : versión de demostración de demostración

Supongo que te gusta

Origin blog.csdn.net/BUG_CONQUEROR_LI/article/details/128232106
Recomendado
Clasificación