2019: essayez les hooks + promets debouncing
Ceci est la version la plus récente de la façon dont je résoudrais ce problème. J'utiliserais:
Il s'agit d'un câblage initial, mais vous composez vous-même des blocs primitifs et vous pouvez créer votre propre crochet personnalisé de sorte que vous n'ayez besoin de le faire qu'une seule fois.
// 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,
};
};
Et puis vous pouvez utiliser votre crochet:
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>
);
};
Vous trouverez cet exemple en cours d'exécution ici et vous devriez lire la documentation react-async-hook pour plus de détails.
2018: essayez de promettre un rebond
Nous voulons souvent rebondir les appels d'API pour éviter d'inonder le backend de requêtes inutiles.
En 2018, travailler avec des rappels (Lodash / Underscore) me fait du mal et est sujet à erreur. Il est facile de rencontrer des problèmes de passe-partout et de concurrence en raison de la résolution d'appels d'API dans un ordre arbitraire.
J'ai créé une petite bibliothèque avec React en tête pour résoudre vos douleurs: awesome-debounce-promise .
Cela ne devrait pas être plus compliqué que cela:
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 });
};
}
La fonction anti-rebond garantit que:
- Les appels d'API seront rejetés
- la fonction anti-rebond renvoie toujours une promesse
- seule la promesse retournée du dernier appel résoudra
- un seul
this.setState({ result });
se produira par appel d'API
Finalement, vous pouvez ajouter une autre astuce si votre composant se démonte:
componentWillUnmount() {
this.setState = () => {};
}
Notez que Observables (RxJS) peut également être un excellent choix pour les entrées anti-rebond, mais c'est une abstraction plus puissante qui peut être plus difficile à apprendre / à utiliser correctement.
<2017: vous voulez toujours utiliser la fonction anti-rebond?
La partie importante ici est de créer une seule fonction anti-rebond (ou limitée) par instance de composant . Vous ne voulez pas recréer la fonction anti-rebond (ou limitation) à chaque fois, et vous ne voulez pas que plusieurs instances partagent la même fonction anti-rebond.
Je ne définis pas de fonction anti-rebond dans cette réponse car elle n'est pas vraiment pertinente, mais cette réponse fonctionnera parfaitement avec le _.debounce
soulignement ou le lodash, ainsi qu'avec toute fonction anti-rebond fournie par l'utilisateur.
BONNE IDÉE:
Parce que les fonctions anti-rebond sont avec état, nous devons créer une fonction anti-rebond par instance de composant .
ES6 (propriété de classe) : recommandé
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6 (constructeur de classe)
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);
},
});
Voir JsFiddle : 3 instances produisent 1 entrée de journal par instance (ce qui en fait 3 globalement).
Pas une bonne idée:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
Cela ne fonctionnera pas, car lors de la création de l'objet description de classe, ce this
n'est pas l'objet créé lui-même. this.method
ne renvoie pas ce que vous attendez car le this
contexte n'est pas l'objet lui-même (qui en réalité n'existe pas encore vraiment BTW car il vient d'être créé).
Pas une bonne idée:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
Cette fois, vous créez effectivement une fonction anti-rebond qui appelle votre this.method
. Le problème est que vous le recréez à chaque debouncedMethod
appel, donc la fonction anti-rebond nouvellement créée ne sait rien des anciens appels! Vous devez réutiliser la même fonction anti-rebond au fil du temps, sinon le rebouncing ne se produira pas.
Pas une bonne idée:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
C'est un peu délicat ici.
Toutes les instances montées de la classe partageront la même fonction anti-rebond, et le plus souvent ce n'est pas ce que vous voulez!. Voir JsFiddle : 3 instances ne produisent qu'une seule entrée de journal dans le monde.
Vous devez créer une fonction anti-rebond pour chaque instance de composant , et non une seule fonction anti-rebond au niveau de la classe, partagée par chaque instance de composant.
Prenez soin de la mise en commun des événements de React
Ceci est lié au fait que nous voulons souvent rebondir ou limiter les événements DOM.
Dans React, les objets d'événement (c'est-à-dire SyntheticEvent
) que vous recevez dans les rappels sont regroupés (cela est maintenant documenté ). Cela signifie qu'après l'appel du rappel d'événement, le SyntheticEvent que vous recevez sera remis dans le pool avec des attributs vides pour réduire la pression du GC.
Ainsi, si vous accédez aux SyntheticEvent
propriétés de manière asynchrone par rapport au rappel d'origine (comme cela peut être le cas si vous accélérez / rebondissez), les propriétés auxquelles vous accédez peuvent être effacées. Si vous souhaitez que l'événement ne soit jamais remis dans le pool, vous pouvez utiliser la persist()
méthode.
Sans persister (comportement par défaut: événement groupé)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Le 2ème (async) s'imprimera hasNativeEvent=false
car les propriétés de l'événement ont été nettoyées.
Avec persister
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Le 2ème (async) s'imprimera hasNativeEvent=true
car persist
vous permet d'éviter de remettre l'événement dans le pool.
Vous pouvez tester ces 2 comportements ici: JsFiddle
Lisez la réponse de Julen pour un exemple d'utilisation persist()
avec une fonction de limitation / anti-rebond.
debounce
. ici, quandonChange={debounce(this.handleOnChange, 200)}/>
, il invoquera àdebounce function
chaque fois. mais, en fait, ce dont nous avons besoin est d'invoquer la fonction que la fonction anti-rebond a renvoyée.