Cómo usar Debounce y Throttle en React y abstraerlos en Hooks

Los Hooks son una adición brillante a React. Simplifican mucha lógica que anteriormente tenía que dividirse en diferentes ciclos de vida con classcomponentes.

Sin embargo, requieren un modelo mental diferente , especialmente para los principiantes.

También grabé una breve serie de videos sobre este artículo que puede resultarle útil.

Rebote y aceleración

Hay un montón de publicaciones de blog escritas sobre antirrebote y aceleración, así que no me sumergiré en cómo escribir tu propio antirrebote y aceleración. Por brevedad, considere debouncey throttlede Lodash.

Si necesita un repaso rápido, ambos aceptan una función (devolución de llamada) y un retraso en milisegundos (digamos x) y luego ambos devuelven otra función con algún comportamiento especial:

  • debounce: Devuelve una función que se puede llamar cualquier número de veces (posiblemente en sucesiones rápidas), pero sólo se invocar la devolución de llamada después de esperar durante xms de la última llamada.
  • throttle: devuelve una función que se puede llamar cualquier número de veces (posiblemente en una sucesión rápida) pero solo invocará la devolución de llamada como máximo una vez cada xms.

Caso de uso

Tenemos un editor de blog mínimo (aquí está el repositorio de GitHub) y nos gustaría guardar la publicación del blog en la base de datos 1 segundo después de que el usuario deja de escribir.

También puede consultar este cuadro de códigos si desea ver la versión final del código.

Una versión mínima de nuestro editor se ve así:

import React, { useState } from 'react'; import debounce from 'lodash.debounce'; function App() { const [value, setValue] = useState(''); const [dbValue, saveToDb] = useState(''); // would be an API call normally const handleChange = event => { setValue(event.target.value); }; return (  

Blog

Editor (Client)

{value}

Saved (DB)

{dbValue} ); }

Aquí, en saveToDbrealidad sería una llamada API al backend. Para simplificar las cosas, lo guardo en estado y luego lo renderizo como dbValue.

Dado que solo queremos realizar esta operación de guardado una vez que el usuario haya dejado de escribir (después de 1 segundo), esto debe eliminarse .

Aquí está el repositorio y la rama del código de inicio.

Creando una función antirrebote

En primer lugar, necesitamos una función antirrebote que envuelva la llamada a saveToDb:

import React, { useState } from 'react'; import debounce from 'lodash.debounce'; function App() { const [value, setValue] = useState(''); const [dbValue, saveToDb] = useState(''); // would be an API call normally const handleChange = event => { const { value: nextValue } = event.target; setValue(nextValue); // highlight-starts const debouncedSave = debounce(() => saveToDb(nextValue), 1000); debouncedSave(); // highlight-ends }; return {/* Same as before */}; } 

Pero, esto en realidad no funciona porque la función debouncedSavese crea nueva en cada handleChangellamada. Esto terminará eliminando cada pulsación de tecla en lugar de eliminar todo el valor de entrada.

useCallback

useCallbackse usa comúnmente para optimizar el rendimiento al pasar devoluciones de llamada a componentes secundarios. Pero podemos usar su restricción de memorizar una función de devolución de llamada para asegurar que las debouncedSavereferencias tengan la misma función antirrebote en todas las representaciones.

También escribí este artículo aquí en freeCodeCamp si desea comprender los conceptos básicos de la memorización.

Esto funciona como se esperaba:

import React, { useState, useCallback } from 'react'; import debounce from 'lodash.debounce'; function App() { const [value, setValue] = useState(''); const [dbValue, saveToDb] = useState(''); // would be an API call normally // highlight-starts const debouncedSave = useCallback( debounce(nextValue => saveToDb(nextValue), 1000), [], // will be created only once initially ); // highlight-ends const handleChange = event => { const { value: nextValue } = event.target; setValue(nextValue); // Even though handleChange is created on each render and executed // it references the same debouncedSave that was created initially debouncedSave(nextValue); }; return {/* Same as before */}; } 

useRef

useRefnos da un objeto mutable cuya currentpropiedad se refiere al valor inicial pasado. Si no lo cambiamos manualmente, el valor persistirá durante toda la vida útil del componente.

Esto es similar a las propiedades de instancia de clase (es decir, definir métodos y propiedades en this).

Esto también funciona como se esperaba:

import React, { useState, useRef } from 'react'; import debounce from 'lodash.debounce'; function App() { const [value, setValue] = useState(''); const [dbValue, saveToDb] = useState(''); // would be an API call normally // This remains same across renders // highlight-starts const debouncedSave = useRef(debounce(nextValue => saveToDb(nextValue), 1000)) .current; // highlight-ends const handleChange = event => { const { value: nextValue } = event.target; setValue(nextValue); // Even though handleChange is created on each render and executed // it references the same debouncedSave that was created initially debouncedSave(nextValue); }; return {/* Same as before */}; } 

Continúe leyendo en mi blog sobre cómo abstraer estos conceptos en ganchos personalizados o consulte la serie de videos.

You may also follow me on Twitter to stay updated on my latest posts. I hope you found this post helpful. :)