Як використовувати Debounce та Throttle у React та абстрагувати їх у гачки

Гачки - блискуче доповнення React. Вони спрощують багато логіки, яку раніше доводилося розділяти на різні життєві цикли з classкомпонентами.

Однак вони вимагають іншої ментальної моделі, особливо для першокласників.

Я також записав короткий відеосюжет за цією статтею, який вам може бути корисним.

Розряд і дросель

Існує маса публікацій у блозі, написаних про розрив і дросель, тому я не буду занурюватися в те, як писати свій власний розвід і дросель. Для стислості розглянемо debounceі throttleвід Lodash.

Якщо вам потрібно швидке оновлення, обидва приймають функцію (зворотного виклику) і затримку в мілісекундах (скажімо x), а потім обидва повертають іншу функцію з якоюсь особливою поведінкою:

  • debounce: Повертає функцію , яку можна назвати будь-яке число раз (можливо , в швидких сукцесіях) , але буде викликати тільки функцію зворотного виклику після очікування в xмс від останнього виклику.
  • throttle: повертає функцію, яку можна викликати будь-яку кількість разів (можливо, швидко), але вона буде викликати зворотний виклик не більше одного разу на кожну xмс.

Використовуйте

У нас є мінімальний редактор блогу (ось репо GitHub), і ми хотіли б зберегти допис у блозі через 1 секунду після того, як користувач припинить друкувати.

Ви також можете звернутися до цього Codesandbox, якщо хочете побачити остаточну версію коду.

Мінімальна версія нашого редактора виглядає так:

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} ); }

Тут saveToDbнасправді був би виклик API до серверної бази. Щоб зробити речі простішими, я зберігаю їх у стані, а потім передаю як dbValue.

Оскільки ми хочемо виконати цю операцію збереження лише після того, як користувач перестане вводити текст (через 1 секунду), це слід скасувати .

Ось репо та гілка початкового коду.

Створення розв'язаної функції

Перш за все, нам потрібна розв'язана функція, яка обертає виклик 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 */}; } 

Але це насправді не працює, оскільки функція debouncedSaveстворюється свіжо при кожному handleChangeвиклику. Це призведе до зняття кожного натискання клавіші, а не до зняття всього вхідного значення.

useCallback

useCallbackзазвичай використовується для оптимізації продуктивності при передачі зворотних викликів дочірнім компонентам. Але ми можемо використовувати його обмеження запам'ятовування функції зворотного виклику, щоб забезпечити debouncedSaveпосиланням ту саму розірвану функцію в рендерах.

Я також написав цю статтю тут на freeCodeCamp, якщо ви хочете зрозуміти основи мемоїзації.

Це працює як слід:

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

useRefдає нам змінний об'єкт, currentвластивість якого відноситься до переданого початкового значення. Якщо ми не змінимо його вручну, значення зберігатиметься протягом усього життя компонента.

Це схоже на властивості екземпляра класу (тобто визначення методів та властивостей на this).

Це також працює як слід:

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 */}; } 

Продовжуйте читати в моєму блозі, як абстрагувати ці поняття у власні гачки або переглянути відеосерію.

Ви також можете стежити за мною у Twitter, щоб залишатися в курсі останніх публікацій. Сподіваюсь, ця публікація вам знайшла користь. :)