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>
);
};
Вы найдете этот пример запущенным здесь, и вы должны прочитать документацию о реакции-асинхронности для получения более подробной информации.
2018: попробуйте обещать разоблачение
Мы часто хотим отменить вызовы API, чтобы избежать затопления серверной части бесполезными запросами.
В 2018 году работа с обратными вызовами (Lodash / Underscore) кажется мне плохой и подверженной ошибкам. Проблемы с параллельным доступом и параллелизмом легко возникают из-за разрешения вызовов API в произвольном порядке.
Я создал небольшую библиотеку с React для решения ваших проблем: awesome-debounce-обещание .
Это не должно быть сложнее, чем это:
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 = () => {};
}
Обратите внимание, что Observables (RxJS) также могут отлично подходить для разбора входных данных, но это более мощная абстракция, которую может быть сложнее правильно изучить / использовать.
<2017: все еще хотите использовать обратный вызов?
Важной частью здесь является создание единой дебазированной (или регулируемой) функции для каждого экземпляра компонента . Вы не хотите каждый раз воссоздавать функцию debounce (или throttle), и вы не хотите, чтобы несколько экземпляров использовали одну и ту же функцию 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
контекст не является самим объектом (который на самом деле еще не существует, кстати, поскольку он только создается).
Не хорошая идея:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
На этот раз вы эффективно создаете дебагованную функцию, которая вызывает вашу this.method
. Проблема в том, что вы воссоздаете его при каждом debouncedMethod
вызове, поэтому вновь созданная функция debounce ничего не знает о предыдущих вызовах! Вы должны повторно использовать одну и ту же отклоненную функцию с течением времени, иначе удаление не произойдет.
Не хорошая идея:
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);
};
2-й (асинхронный) будет напечатан, hasNativeEvent=false
потому что свойства события были очищены.
С упорством
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
2-й (асинхронный) будет печатать, hasNativeEvent=true
потому чтоpersist
позволяет избежать возврата события в пул.
Вы можете проверить эти 2 поведения здесь: JsFiddle
Прочитайте ответ Джулена для примера использования persist()
с функцией throttle / debounce.
debounce
. здесь, когдаonChange={debounce(this.handleOnChange, 200)}/>
он будет вызыватьdebounce function
каждый раз. но на самом деле нам нужно вызвать функцию, возвращаемую функцией debounce.