¿Cuándo usar useCallback, useMemo y useEffect?

useCallback, useMemo y useEffect son una forma de optimizar el rendimiento de las aplicaciones basadas en React entre la renderización de componentes. Estas funciones brindan algunas de las características de los componentes basados ​​en clases, como la persistencia de estados dedicados a través de llamadas de procesamiento, así como las funciones del ciclo de vida para controlar cómo se ven los componentes en varias etapas de su ciclo de vida.

Para responder cuándo usar useCallBack, useMemo y useEffect, debemos saber qué hacen exactamente y en qué se diferencian.

  1. useCallback : useCallback es un gancho de reacción que devuelve una devolución de llamada memorizada cuando se le pasa una función y una lista de dependencias como parámetros. Es muy útil cuando un componente pasa una devolución de llamada a su componente secundario para evitar la representación del componente secundario. Solo cambia la devolución de llamada cuando se cambia una de sus dependencias.

  2. useMemo : useMemo es similar al gancho useCallback ya que acepta una función y una lista de dependencias pero devuelve el valor memorizado devuelto por la función pasada. Recalculaba el valor solo cuando cambiaba una de sus dependencias. Es útil para evitar cálculos costosos en cada renderizado cuando el valor devuelto no va a cambiar.

  3. useEffect : un gancho que nos ayuda a realizar mutaciones, suscripciones, temporizadores, registros y otros efectos secundarios después de que se hayan renderizado todos los componentes. El useEffect acepta una función que es de naturaleza imperativa y una lista de dependencias. Cuando sus dependencias cambian, ejecuta la función pasada.

Crear una aplicación de reacción para comprender los tres ganchos:

  • Paso 1: Cree una aplicación React usando el siguiente comando:

    npx create-react-app usecallbackdemo
  • Paso 2: después de crear la carpeta de su proyecto, es decir , el nombre de la carpeta , acceda a ella con el siguiente comando:

    cd usecallbackdemo

Estructura del proyecto: Tendrá el siguiente aspecto.

La estructura del proyecto

Ahora comprendamos el funcionamiento de los tres ganchos.

1. usecallback: Depende de la igualdad referencial. En javascript, las funciones son ciudadanos de primera clase, lo que significa que una función es un objeto regular. Por lo tanto, dos objetos de función, incluso cuando comparten el mismo código, son dos objetos diferentes. Solo recuerda que un objeto función es referencialmente igual a sí mismo.

Veamos esto en el siguiente código, doubleFactory crea y devuelve una función:

Javascript

function doubleFactory(){
    return (a) => 2*a;
}
  
const double1 = doubleFactory();
const double2 = doubleFactory();
  
double1(8); // gives 16
double2(8); // gives 16
  
double1 === double2;  // false
double1 === double1;  // true

doube1 y double2 duplican el valor que se les pasa y son creados por la misma función de fábrica. Las dos funciones, incluso cuando comparten el mismo código, no son iguales, aquí ( doble1 === doble2) se evalúa como falso. 

Cuándo usar useCallback: en React, un componente generalmente tiene alguna función de devolución de llamada creada dentro de él.

Javascript

function MyComponent(){
  
    // HandleChange is created on every render
    const handleChange = () => {...};
      
    return <> 
        ... 
        </>;
}

Aquí los objetos de la función handleChange son diferentes en cada representación de MyComponent. Y hay varios casos en los que podemos querer el mismo objeto de función entre múltiples representaciones. Por ejemplo, cuando es una dependencia para algunos otros ganchos (useEffect(…, callbackfunc)) o cuando el objeto de la función en sí tiene algún estado interno que debemos mantener. En tal caso, useCallback hook es útil. En palabras simples, useCallback( callBackFun, deps ) devuelve una devolución de llamada memorizada cuando los valores de dependencia deps no cambian entre representaciones. (memorizado aquí se refiere a almacenar en caché el objeto para uso futuro).

Veamos un caso de uso usando un proyecto: La aplicación consta de un campo de entrada, un botón y una lista. La lista es un componente que consta de dos números, el primero es ingresar más 10 y el segundo es ingresar + 100. El botón cambia los componentes del modo oscuro al modo claro y viceversa.

Habrá dos componentes App y List, App es nuestro componente principal donde agregamos el campo de entrada, el botón y la Lista. El componente de lista se utiliza para imprimir la lista de elementos según el campo de entrada.

App.jsx

import React, { useState} from "react"
import List from "./List"
  
function App(){
  
    {/* Initial states */}
    const [input, setInput] = useState(1);
    const [light, setLight] = useState(true);
  
    {/* getItems() returns a list of number which
    is number+10 and number + 100 */}
    const getItems = () => {
        return [input + 10, input + 100];
    }
  
    {/* Style for changing the theme */}
    const theme = {
        backgroundColor: light ? "White": "grey",
        color: light ? "grey" : "white"
    }
     
    return <>
        {/* set the theme in the parent div */}
        <div style={theme}>
            <input type="number"
            value={input}
  
            {/* When we input a number it is stored
            in our stateful variable */} 
            onChange={event => setInput(parseInt(event.target.value))} />
  
            {/* on click the button the theme is set to the 
            opposite mode, light to dark and vice versa*/}
            <button onClick={() => setLight(prevLight => !prevLight)}>
            {light ? "dark mode":"light mode"}
            </button>
            <List getItems={getItems} />
        </div>
    </>;
}
  
export default App;

List.jsx

import React, { useEffect, useState } from "react"
  
function List({ getItems }){
  
    /* Initial state of the items */
    const [items, setItems] = useState([]);
  
    /* This hook sets the value of items if 
       getItems object changes */
    useEffect(() => {
        console.log("Fetching items");
        setItems(getItems());
    }, [getItems]);
  
    /* Maps the items to a list */
    return <div>
        {items.map(item => <div key={item}>{item}</div>)}
    </div>
}
export default List;

Explicación: el componente de lista obtiene la función getItems como propiedad. Cada vez que el objeto de función getItems cambie, useEffect llamará a setItems para configurar la lista devuelta del objeto de función a elementos variables con estado y luego mapearemos esos elementos en una lista de div. elementos” para ver con qué frecuencia se recuperan los elementos .

Paso para ejecutar la aplicación:

npm start

Producción:

Explicación: Lo siguiente será el resultado cuando un usuario ingrese un número en el campo de entrada. Se puede ver en el registro de la consola que cuando la aplicación se procesa por primera vez, se obtienen elementos y se imprimen «obteniendo elementos». Ahora, si ingresamos algunos números diferentes, vemos que los elementos se recuperan una vez más.

Ahora, lo extraño es que, cuando presionamos el botón para cambiar el tema, vemos que los elementos aún se están recuperando incluso cuando el campo de entrada no se modifica.

La razón detrás de este comportamiento es que cuando presionamos el botón, el componente de la aplicación se vuelve a representar y, por lo tanto, la función getItems() dentro de la aplicación se crea nuevamente y sabemos que dos objetos son referencialmente diferentes. Por lo tanto, dentro del componente List, useEffect hook llama a setItems e imprime «Obteniendo elementos» ya que su dependencia ha cambiado.

La solución al problema anterior: aquí podemos usar la función useCallback para memorizar la función getItems() dependiendo del número de entrada. No queremos volver a crear la función a menos que cambie la entrada y, por lo tanto, al presionar el botón (cambiar el tema) no se recuperarán los elementos.

App.jsx

import React, { useCallback, useState} from "react"
import List from "./List"
  
function App(){
  
    {/* Initial states */}
    const [input, setInput] = useState(1);
    const [light, setLight] = useState(true);
  
    {/* useCallback memoizes the getItems() which 
       returns a list of number which is number+10
       and number + 100 */}
    const getItems = useCallback(() => {
        return [input + 10, input + 100];
    }, [input]);
  
    {/* style for changing the theme */}
    const theme = {
        backgroundColor: light ? "White": "grey",
        color: light ? "grey" : "white"
    }
      
  
    return <>
        {/* set the theme in the parent div */}
        <div style={theme}>
            <input type="number"
            value={input}
  
            {/* When we input a number it is stored in
            our stateful variable */} 
            onChange={event => 
            setInput(parseInt(event.target.value))
            } />
  
            {/* on click the button the theme is set to 
            the opposite mode, light to dark and vice versa*/}
            <button onClick={() => 
            setLight(prevLight => 
            !prevLight)}>{light ? "dark mode":"light mode"}
            </button>
            <List getItems={getItems} />
        </div>
    </>;
}
  
export default App;

Ahora usamos el enlace useCallback para memorizar la función getitems que toma la función y una lista de dependencias. La lista de dependencias en nuestro caso incluye solo la entrada.

Producción:

Explicación: se puede ver en la salida que los elementos se recuperan solo una vez cuando se procesa la aplicación, pero no cuando cambiamos el tema presionando el botón. No importa cuántas veces cambiemos el tema, useEffect no llamará a setItems hasta que el campo de entrada tenga un nuevo número.

2. useMemo: el enlace useMemo devuelve un valor memorizado después de tomar una función y una lista de dependencias. Devuelve el valor almacenado en caché si las dependencias no cambian. De lo contrario, volverá a calcular el valor utilizando la función pasada.

Cuándo usar useMemo:

Hay dos casos en los que usar useMemo puede ser útil:

  1. Cuando un componente usa un valor calculado usando una función que requiere mucho tiempo.

    MiComponente.jsx

    function MyComponent(){
        const [data, setData] = useState(0);
        const number = verySlowFunction(data);
        return <div>{number}</div>;
    }
      
    function verySlowFunction(input){
        ...heavy work done here
        return value;
    }

    Aquí se llama a la función lenta cada vez que se procesa MyComponent , tal vez porque se cambia alguna variable con estado o algún otro componente causó la representación.

    Solución: Al memorizar el valor devuelto de la función lenta usando el gancho useMemo podemos salvarnos del retraso que puede causar.

    MiComponente.jsx

    function MyComponent(){
        const [data, setData] = useState(0);
        const number = useMemo(() => {
            return verySlowFunction(data)}, [data]);
          
        return <div>{number}</div>;
    }
      
    function verySlowFunction(input){
        ...heavy work done here
        return value;
    }

    aquí usamos el enlace useMemo para almacenar en caché el valor devuelto y la lista de dependencias contiene la variable con estado de datos. Ahora, cada vez que se procesa el componente, si la variable de datos no se modifica, obtenemos el valor memorizado sin llamar a la función de uso intensivo de la CPU. Por lo tanto, mejora el rendimiento.

  2. Ahora considere otro escenario cuando tenemos un componente que hace algo cuando cambian algunos datos, por ejemplo, tomemos el gancho useEffect que registra si cambia alguna dependencia.

    MiComponente.jsx

    function MyComponent() {    
        const [number, setNumber] = useState(0);
        const data = {
            key: value
        };
          
        useEffect(() => {
            console.log('Hello world');
        }, [data]);
    }

    En el código anterior, cada vez que se procesa el componente, se imprime «Hola mundo» en la consola debido al hecho de que el objeto de datos que se almacena en el procesamiento anterior es referencialmente diferente en el siguiente procesamiento y, por lo tanto, el enlace useEffect ejecuta el función consola.log. En el mundo real, useEffect puede contener alguna funcionalidad que no queremos que se repita si sus dependencias no cambian.

    Solución: podemos memorizar el objeto de datos usando el enlace useMemo para que la representación del componente no cree un nuevo objeto de datos y, por lo tanto, useEffect no llame a su cuerpo.

    MiComponente.jsx

    function MyComponent(){
          
        const [number, setNumber] = useState(0);
      
        const data = useMemo(() => {
        return {
            key: value
        }}, number);
          
        useEffect(() => {
            console.log('Hello world');
        }, [data]);
    }

    Ahora, cuando el componente se representa por segunda vez y si la variable con estado del número no se modifica, entonces no se ejecuta console.log().

3. useEffect: en react, los efectos secundarios de algunos cambios de estado no están permitidos en los componentes funcionales. Para realizar una tarea una vez que se completa el renderizado y algunos cambios de estado, podemos usar useEffect. Este gancho toma una función para ser ejecutada y una lista de dependencias, cambiando cuál causará la ejecución del cuerpo del gancho.

Entender su uso adecuado. veamos un ejemplo sencillo:

Ejemplo: considere un escenario en el que tenemos que obtener algunos datos de alguna API una vez que se montan los componentes. En el código de ejemplo, simulamos el servidor con nuestro objeto de datos con valores de diferentes colores y frutas. Queremos imprimir la lista de elementos dependiendo del botón que se presione. Por lo tanto, tenemos dos variables de estado elección actual y elementos que se modifican presionando los botones. Cuando se presiona un botón, cambia la elección actualy se llama al cuerpo de useEffect y los elementos de la elección actual se imprimen usando un mapa. Ahora, si no usamos useEffect, cada vez que se presione un botón, se obtendrán datos del servidor incluso si la elección no cambia. En tal condición, este enlace nos ayuda a no llamar a la lógica de búsqueda a menos que cambie nuestra elección.

App.jsx

import React, { useEffect, useState} from "react"
  
  
function App(){
  
    /* Some data */
    const data = {
        Colors: ["red", "green", "yellow"],
        Fruits: ["Apple", "mango", "Banana"]
    }
  
    /* Initial states */
    const [currentChoice, setCurrentChoice] = useState("Colors");
    const [items, setItems] = useState([]);
  
    /* Using useEffect to set the data of currentchoice
       to items and console log the fetching... */
    useEffect(() => {
        setItems(data[currentChoice]);
        console.log("Data is fetched!");
    }, [currentChoice]);
      
    return <>
    <button onClick={() => setCurrentChoice("Colors")}>Colors</button>
    <button onClick={() => setCurrentChoice("Fruits")}>Fruits</button>    
        {items.map(item => {return <div key={item}>{item}</div>})}
    </>;
}
  
export default App;

Producción:

Explicación:   cuando la aplicación se carga por primera vez, los datos se obtienen de nuestro servidor falso. Esto se puede ver en la consola en la imagen de abajo. Y cuando presionamos el botón Fruits, los datos apropiados se recuperan nuevamente del servidor y podemos ver que «Data is fetched» se imprime nuevamente en la consola. Pero si presionamos el botón Frutas de nuevo, no tenemos que volver a obtener los datos del servidor ya que nuestro estado de elección no cambia.

Conclusion:

Por lo tanto, se debe usar un enlace useCallback cuando queremos memorizar una devolución de llamada, y para memorizar el resultado de una función para evitar cálculos costosos, podemos usar useMemo. useEffect se usa para producir efectos secundarios en algunos cambios de estado. Una cosa para recordar es que uno no debe abusar de los ganchos. 

Publicación traducida automáticamente

Artículo escrito por ganesh227 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *