2019: спробуйте гачки + обіцяйте дебютацію
Це найсучасніша версія того, як я вирішував би цю проблему. Я б використав:
Це кілька початкових підключень, але ви складаєте примітивні блоки самостійно, і ви можете зробити власний власний гачок, щоб вам це потрібно зробити лише один раз.
// Generic reusable hook
const useDebouncedSearch = (searchFunction) => {
// Handle the input text state
const [inputText, setInputText] = useState('');
// Debounce the original search async function
const debouncedSearchFunction = useConstant(() =>
AwesomeDebouncePromise(searchFunction, 300)
);
// The async callback is run each time the text changes,
// but as the search function is debounced, it does not
// fire a new request on each keystroke
const searchResults = useAsync(
async () => {
if (inputText.length === 0) {
return [];
} else {
return debouncedSearchFunction(inputText);
}
},
[debouncedSearchFunction, inputText]
);
// Return everything needed for the hook consumer
return {
inputText,
setInputText,
searchResults,
};
};
І тоді ви можете використовувати свій гачок:
const useSearchStarwarsHero = () => useDebouncedSearch(text => searchStarwarsHeroAsync(text))
const SearchStarwarsHeroExample = () => {
const { inputText, setInputText, searchResults } = useSearchStarwarsHero();
return (
<div>
<input value={inputText} onChange={e => setInputText(e.target.value)} />
<div>
{searchResults.loading && <div>...</div>}
{searchResults.error && <div>Error: {search.error.message}</div>}
{searchResults.result && (
<div>
<div>Results: {search.result.length}</div>
<ul>
{searchResults.result.map(hero => (
<li key={hero.name}>{hero.name}</li>
))}
</ul>
</div>
)}
</div>
</div>
);
};
Ви знайдете цей приклад, який працює тут, і вам слід ознайомитися з документацією на react-async-hook, щоб отримати докладнішу інформацію.
2018: спробуйте обіцяти дебютацію
Ми часто хочемо відмовитись від дзвінків API, щоб уникнути заливання бекенда непотрібними запитами.
У 2018 році робота з зворотними дзвінками (Lodash / Underscore) відчуває мене погано і схильна до помилок. Складно зіткнутися з проблемами котла та паралельними технологіями за рахунок вирішення дзвінків API у довільному порядку.
Я створив невелику бібліотеку з React на увазі, щоб вирішити ваші болі: awesome-debounce-obence .
Це не повинно бути складніше, ніж це:
const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));
const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);
class SearchInputAndResults extends React.Component {
state = {
text: '',
results: null,
};
handleTextChange = async text => {
this.setState({ text, results: null });
const result = await searchAPIDebounced(text);
this.setState({ result });
};
}
Дебютована функція забезпечує:
- Виклики API будуть зняті
- дебютована функція завжди повертає обіцянку
- вирішиться лише повернута обіцянка останнього дзвінка
- один
this.setState({ result });
виклик відбуватиметься за виклик API
Зрештою ви можете додати ще один трюк, якщо ваш компонент відключений:
componentWillUnmount() {
this.setState = () => {};
}
Зауважте, що спостережувані дані (RxJS) також можуть бути чудовим пристосуванням для денонсації входів, але це більш потужна абстракція, яка може бути важче навчитися / використовувати правильно.
<2017: все ще хочете використовувати зворотний дзвінок?
Важливою частиною тут є створення єдиної дебютованої (або приглушеної) функції для кожного компонента . Ви не хочете кожен раз відтворювати функцію debounce (або дросель), і ви не хочете, щоб жоден з декількох екземплярів ділив одну і ту ж функцію, яку деблокували.
Я не визначаю функцію деблокування в цій відповіді, оскільки це не дуже актуально, але ця відповідь буде прекрасно спрацьовувати з _.debounce
підкресленням або подачею, а також з будь-якою функцією розблокування, наданою користувачем.
ГАРНА ІДЕЯ:
Оскільки функції дебюйованості є стаціонарними, ми повинні створити одну дебютовану функцію для кожного компонента .
ES6 (властивість класу) : рекомендовано
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6 (конструктор класу)
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.method = debounce(this.method.bind(this),1000);
}
method() { ... }
}
ES5
var SearchBox = React.createClass({
method: function() {...},
componentWillMount: function() {
this.method = debounce(this.method.bind(this),100);
},
});
Див. JsFiddle : 3 екземпляри виробляють 1 запис журналу на екземпляр (що складає 3 у всьому світі).
НЕ гарна ідея:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
Це не спрацює, тому що під час створення об’єкта опису класу this
це не сам об’єкт, створений. this.method
не повертає те, що ви очікуєте, оскільки this
контекст не є самим об'єктом (який насправді ще не існує BTW, оскільки він тільки створюється).
НЕ гарна ідея:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
Цього разу ви фактично створюєте функцію, що знімається, яка викликає вашу this.method
. Проблема полягає в тому, що ви відтворюєте його під час кожного debouncedMethod
дзвінка, тому новостворена функція дебютування нічого не знає про колишні дзвінки! Потрібно повторно використовувати ту саму функцію, яку деблокували, інакше деблокування не відбудеться.
НЕ гарна ідея:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
Це тут трохи хитро.
Усі змонтовані екземпляри класу матимуть однакову функцію дебютування, і найчастіше це не те, що потрібно! Див. JsFiddle : 3 екземпляри виробляють лише 1 запис у всьому світі.
Ви повинні створити розблоковувану функцію для кожного екземпляра компонента , а не одну функцію дебютування на рівні класу, що поділяється між кожним екземпляром компонента.
Подбайте про об'єднання подій React
Це пов'язано з тим, що ми часто хочемо знімати або придушувати події DOM.
У React об'єкти подій (тобто SyntheticEvent
), які ви отримуєте у зворотній зв'язок, об'єднані (це зараз задокументовано ). Це означає, що після виклику зворотного виклику події SyntheticEvent, який ви отримаєте, буде повернутий у пул із порожніми атрибутами для зниження тиску GC.
Отже, якщо ви отримуєте доступ до SyntheticEvent
властивостей асинхронно до оригінального зворотного дзвінка (як це може бути у випадку, якщо ви перемикаєте / відключаєте), властивості, до яких ви отримуєте доступ, можуть бути стерті. Якщо ви хочете, щоб подія ніколи не була повернена в пул, ви можете скористатися persist()
методом.
Без збереження (поведінка за замовчуванням: об'єднана подія)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Друга (асинхронізація) буде надрукована, hasNativeEvent=false
оскільки очищені властивості події.
З наполегливістю
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Друга (асинхронізація) буде надрукована, hasNativeEvent=true
оскільки persist
дозволяє уникнути повернення події в пул.
Ви можете перевірити ці 2 поведінки тут: JsFiddle
Прочитайте відповідь Юлена на прикладі використання persist()
з функцією дросельної заслінки / дебютації.
debounce
. тут, колиonChange={debounce(this.handleOnChange, 200)}/>
, він буде викликатиdebounce function
кожен раз. але насправді нам потрібно викликати функцію, яку повернула функція дебютації.