2019: versuchen Sie Hooks + Versprechen Entprellen
Dies ist die aktuellste Version, wie ich dieses Problem lösen würde. Ich würde ... benutzen:
Dies ist eine anfängliche Verkabelung, aber Sie erstellen selbst primitive Blöcke, und Sie können Ihren eigenen benutzerdefinierten Hook erstellen, sodass Sie dies nur einmal tun müssen.
// 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,
};
};
Und dann können Sie Ihren Haken benutzen:
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>
);
};
Dieses Beispiel wird hier ausgeführt. Weitere Informationen finden Sie in der Dokumentation zum React-Async-Hook .
2018: Versuchen Sie, das Versprechen zu entprellen
Wir möchten häufig API-Aufrufe entprellen, um zu vermeiden, dass das Backend mit nutzlosen Anforderungen überflutet wird.
Im Jahr 2018 fühlt sich die Arbeit mit Rückrufen (Lodash / Underscore) für mich schlecht und fehleranfällig an. Aufgrund von API-Aufrufen, die in einer beliebigen Reihenfolge gelöst werden, können Probleme mit Boilerplate und Parallelität leicht auftreten.
Ich habe eine kleine Bibliothek mit dem Gedanken an React erstellt, um Ihre Probleme zu lösen: Awesome-Debounce-Versprechen .
Dies sollte nicht komplizierter sein:
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 });
};
}
Die entprellte Funktion stellt sicher, dass:
- API-Aufrufe werden entprellt
- Die entprellte Funktion gibt immer ein Versprechen zurück
- Nur das vom letzten Anruf zurückgegebene Versprechen wird gelöst
this.setState({ result });
Pro API-Aufruf wird eine einzige ausgeführt
Möglicherweise können Sie einen weiteren Trick hinzufügen, wenn die Bereitstellung Ihrer Komponente aufgehoben wird:
componentWillUnmount() {
this.setState = () => {};
}
Beachten Sie, dass Observables (RxJS) auch gut zum Entprellen von Eingaben geeignet sind, aber eine leistungsfähigere Abstraktion ist, die möglicherweise schwieriger zu erlernen / richtig zu verwenden ist.
<2017: Möchten Sie das Callback-Debouncing weiterhin verwenden?
Der wichtige Teil hierbei ist das Erstellen einer einzelnen entprellen (oder gedrosselten) Funktion pro Komponenteninstanz . Sie möchten die Entprellungs- (oder Drossel-) Funktion nicht jedes Mal neu erstellen, und Sie möchten nicht, dass mehrere Instanzen dieselbe entprellende Funktion gemeinsam nutzen.
Ich definiere in dieser Antwort keine Entprellungsfunktion, da sie nicht wirklich relevant ist, aber diese Antwort funktioniert perfekt mit _.debounce
Unterstrichen oder Lodash sowie mit allen vom Benutzer bereitgestellten Entprellungsfunktionen.
GUTE IDEE:
Da entprellte Funktionen statusbehaftet sind, müssen wir eine entprellte Funktion pro Komponenteninstanz erstellen .
ES6 (Klasseneigenschaft) : empfohlen
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6 (Klassenkonstruktor)
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);
},
});
Siehe JsFiddle : 3 Instanzen erzeugen 1 Protokolleintrag pro Instanz (das sind 3 global).
Keine gute Idee:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
Dies funktioniert nicht, da bei der Klassenbeschreibung die Objekterstellung this
nicht das selbst erstellte Objekt ist. this.method
gibt nicht zurück, was Sie erwarten, da der this
Kontext nicht das Objekt selbst ist (das tatsächlich noch nicht wirklich existiert, übrigens, da es gerade erstellt wird).
Keine gute Idee:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
Dieses Mal erstellen Sie effektiv eine entprellte Funktion, die Ihre aufruft this.method
. Das Problem ist, dass Sie es bei jedem debouncedMethod
Anruf neu erstellen, sodass die neu erstellte Entprellungsfunktion nichts über frühere Anrufe weiß! Sie müssen dieselbe entprellte Funktion im Laufe der Zeit wiederverwenden, da sonst sonst keine Entprellung erfolgt.
Keine gute Idee:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
Das ist hier etwas knifflig.
Alle gemounteten Instanzen der Klasse haben dieselbe entprellte Funktion, und meistens ist dies nicht das, was Sie wollen!. Siehe JsFiddle : 3 Instanzen produzieren weltweit nur 1 Protokolleintrag.
Sie müssen für jede Komponenteninstanz eine entprellte Funktion erstellen und nicht eine einzelne entprellte Funktion auf Klassenebene, die von jeder Komponenteninstanz gemeinsam genutzt wird.
Kümmere dich um das Event-Pooling von React
Dies hängt damit zusammen, dass wir DOM-Ereignisse häufig entprellen oder drosseln möchten.
In React werden die Ereignisobjekte (dh SyntheticEvent
), die Sie in Rückrufen erhalten, zusammengefasst (dies ist jetzt dokumentiert ). Dies bedeutet, dass nach dem Aufruf des Ereignisrückrufs das empfangene SyntheticEvent mit leeren Attributen wieder in den Pool gestellt wird, um den GC-Druck zu verringern.
Wenn Sie also SyntheticEvent
asynchron auf Eigenschaften des ursprünglichen Rückrufs zugreifen (wie dies beim Drosseln / Entprellen der Fall sein kann), werden die Eigenschaften, auf die Sie zugreifen, möglicherweise gelöscht. Wenn Sie möchten, dass das Ereignis nie wieder in den Pool aufgenommen wird, können Sie die persist()
Methode verwenden.
Ohne Persistenz (Standardverhalten: gepooltes Ereignis)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Der zweite (asynchron) wird gedruckt, hasNativeEvent=false
da die Ereigniseigenschaften bereinigt wurden.
Mit bestehen
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Der zweite (asynchron) wird gedruckt, hasNativeEvent=true
da persist
Sie vermeiden können, dass das Ereignis wieder in den Pool verschoben wird .
Sie können diese beiden Verhaltensweisen hier testen : JsFiddle
In Julens Antwort finden Sie ein Beispiel für die Verwendung persist()
mit einer Gas- / Entprellfunktion.
debounce
. hier, wannonChange={debounce(this.handleOnChange, 200)}/>
, wird esdebounce function
jedes Mal aufgerufen . Tatsächlich müssen wir jedoch die Funktion aufrufen, die die Entprellungsfunktion zurückgegeben hat.