2019: wypróbuj haki + zapowiedź obietnicy
To jest najbardziej aktualna wersja tego, jak rozwiązałbym ten problem. Użyłbym:
To jest trochę wstępne okablowanie, ale sam tworzysz prymitywne bloki i możesz zrobić swój własny hak, abyś musiał to zrobić tylko raz.
// 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,
};
};
A potem możesz użyć haka:
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>
);
};
Ten przykład znajduje się tutaj . Aby uzyskać więcej informacji, przeczytaj dokumentację przechwytującą reakcję asynchroniczną .
2018: spróbuj ogłosić obietnicę
Często chcemy ogłaszać wywołania interfejsu API, aby uniknąć zalania zaplecza niepotrzebnymi żądaniami.
W 2018 r. Praca z wywołaniami zwrotnymi (Lodash / Underscore) jest dla mnie zła i podatna na błędy. Łatwo jest napotkać problemy z podstawowymi funkcjami i współbieżnością, ponieważ wywołania API są rozwiązywane w dowolnej kolejności.
Stworzyłem małą bibliotekę z myślą o React, aby rozwiązać wasze problemy: niesamowita obietnica wypowiedzenia .
Nie powinno to być bardziej skomplikowane:
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 });
};
}
Zadeklarowana funkcja zapewnia, że:
- Połączenia API będą ogłaszane
- funkcja zadeklarowana zawsze zwraca obietnicę
- rozwiązana zostanie tylko obietnica zwrócona z ostatniego połączenia
this.setState({ result });
wydarzy się pojedynczy na każde wywołanie API
W końcu możesz dodać kolejną sztuczkę, jeśli twój komponent odmontuje:
componentWillUnmount() {
this.setState = () => {};
}
Zauważ, że Observables (RxJS) może również doskonale nadawać się do ogłaszania danych wejściowych, ale jest to bardziej wydajna abstrakcja, która może być trudniejsza do nauczenia się / używania poprawnie.
<2017: Nadal chcesz korzystać z zapowiedzi wywołania zwrotnego?
Ważną częścią jest tutaj utworzenie jednej zadeklarowanej (lub ograniczonej) funkcji dla każdej instancji komponentu . Nie chcesz ponownie odtwarzać funkcji debounce (lub przepustnicy) za każdym razem i nie chcesz, aby wiele instancji współużytkowało tę samą funkcję debounce.
Nie definiuję funkcji ogłaszającej w tej odpowiedzi, ponieważ nie jest ona tak naprawdę istotna, ale ta odpowiedź będzie doskonale działać z _.debounce
podkreśleniem lub lodash, a także z dowolną funkcją ogłaszającą podaną przez użytkownika.
DOBRY POMYSŁ:
Ponieważ funkcje zapowiadane są stanowe, musimy utworzyć jedną funkcję zapowiedzianą na instancję składnika .
ES6 (właściwość klasy) : zalecane
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6 (konstruktor klasy)
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);
},
});
Zobacz JsFiddle : 3 instancje generują 1 pozycję dziennika na instancję (co daje 3 globalnie).
NIE jest to dobry pomysł:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
To nie zadziała, ponieważ podczas tworzenia opisu klasy obiekt this
nie jest sam obiekt utworzony. this.method
nie zwraca tego, czego oczekujesz, ponieważ this
kontekst nie jest samym obiektem (który tak naprawdę jeszcze nie istnieje BTW, ponieważ właśnie jest tworzony).
NIE jest to dobry pomysł:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
Tym razem efektywnie tworzysz zapowiadaną funkcję, która wywołuje Twoją this.method
. Problem polega na tym, że odtwarzasz go przy każdym debouncedMethod
wywołaniu, więc nowo utworzona funkcja usuwania nie wie nic o poprzednich połączeniach! Z czasem musisz ponownie użyć tej samej funkcji, która została ogłoszona, w przeciwnym razie ogłoszenie nie nastąpi.
NIE jest to dobry pomysł:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
To trochę trudne.
Wszystkie zamontowane instancje klasy będą miały tę samą zapowiedzianą funkcję i najczęściej nie jest to to, czego chcesz! Zobacz JsFiddle : 3 instancje produkują tylko 1 pozycję dziennika na całym świecie.
Musisz utworzyć funkcję, która została zadeklarowana dla każdej instancji składnika , a nie pojedynczą, zadeklarowaną funkcję na poziomie klasy, wspólną dla każdej instancji składnika.
Zadbaj o łączenie zdarzeń React
Jest to powiązane, ponieważ często chcemy ogłosić lub zdławić zdarzenia DOM.
W React obiekty zdarzeń (tj. SyntheticEvent
), Które otrzymujesz w wywołaniach zwrotnych, są pulowane (jest to teraz udokumentowane ). Oznacza to, że po wywołaniu wywołania zwrotnego zdarzenia otrzymane SyntheticEvent zostanie ponownie umieszczone w puli z pustymi atrybutami w celu zmniejszenia presji GC.
Jeśli więc uzyskujesz dostęp do SyntheticEvent
właściwości asynchronicznie do pierwotnego wywołania zwrotnego (jak w przypadku dławienia / usuwania błędów), właściwości, do których masz dostęp, mogą zostać usunięte. Jeśli chcesz, aby zdarzenie nigdy nie było ponownie umieszczane w puli, możesz użyć tej persist()
metody.
Bez utrwalania (zachowanie domyślne: zdarzenie połączone)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Drugi (asynchroniczny) zostanie wydrukowany, hasNativeEvent=false
ponieważ właściwości zdarzenia zostały wyczyszczone.
Z wytrwałością
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Drugi (asynchroniczny) zostanie wydrukowany, hasNativeEvent=true
ponieważ persist
pozwala uniknąć ponownego umieszczenia zdarzenia w puli.
Możesz przetestować te 2 zachowania tutaj: JsFiddle
Przeczytaj odpowiedź Julen, aby zobaczyć przykład korzystania persist()
z funkcji przepustnicy / odbicia.
debounce
. tutaj, kiedyonChange={debounce(this.handleOnChange, 200)}/>
będzie przywoływać zadebounce function
każdym razem. ale w rzeczywistości potrzebujemy wywołać funkcję, którą zwróciła funkcja debounce.