Czy używanie componentDidMount()
jako funkcji asynchronicznej jest dobrą praktyką w React Native, czy powinienem tego unikać?
Muszę uzyskać informacje od AsyncStorage
momentu zamontowania komponentu, ale jedyny znany mi sposób, aby to umożliwić, to ustawienie componentDidMount()
funkcji asynchronicznej.
async componentDidMount() {
let auth = await this.getAuth();
if (auth)
this.checkAuth(auth);
}
Czy jest z tym jakiś problem i czy są jakieś inne rozwiązania tego problemu?
reactjs
asynchronous
react-native
Mirakurun
źródło
źródło
Odpowiedzi:
Zacznijmy od wskazania różnic i określenia, w jaki sposób może to powodować problemy.
Oto kod
componentDidMount()
metody cyklu życia async i „sync” :// This is typescript code componentDidMount(): void { /* do something */ } async componentDidMount(): Promise<void> { /* do something */ /* You can use "await" here */ }
Patrząc na kod, mogę wskazać następujące różnice:
async
słowa kluczowe: W maszynopisie, jest jedynie markerem kod. Robi 2 rzeczy:Promise<void>
zamiastvoid
. Jeśli jawnie określisz typ zwracania jako nieobiecujący (np. Void), maszynopis wypluje na ciebie błąd.await
słów kluczowych wewnątrz metody.void
naPromise<void>
async someMethod(): Promise<void> { await componentDidMount(); }
Możesz teraz użyć
await
słowa kluczowego wewnątrz metody i tymczasowo wstrzymać jej wykonywanie. Lubię to:async componentDidMount(): Promise<void> { const users = await axios.get<string>("http://localhost:9001/users"); const questions = await axios.get<string>("http://localhost:9001/questions"); // Sleep for 10 seconds await new Promise(resolve => { setTimeout(resolve, 10000); }); // This line of code will be executed after 10+ seconds this.setState({users, questions}); return Promise.resolve(); }
Jak mogliby powodować kłopoty?
async
kluczowe jest całkowicie nieszkodliwe.Nie mogę sobie wyobrazić sytuacji, w której trzeba by wywołać
componentDidMount()
metodę, aby typ zwracanyPromise<void>
był również nieszkodliwy.Wywołanie metody mającej zwracany typ
Promise<void>
bezawait
słowa kluczowego nie robi różnicy od wywołania metody mającej zwracany typvoid
.Ponieważ nie ma metod cyklu życia, po
componentDidMount()
opóźnieniu jego wykonania wydaje się całkiem bezpieczny. Ale jest haczyk.Powiedzmy, że powyższe
this.setState({users, questions});
zostanie wykonane po 10 sekundach. W połowie opóźnienia kolejna ...this.setState({users: newerUsers, questions: newerQuestions});
... zostały pomyślnie wykonane, a DOM został zaktualizowany. Wynik był widoczny dla użytkowników. Zegar nadal tykał i upłynęło 10 sekund. Opóźniony
this.setState(...)
byłby wtedy wykonywany, a DOM zostałby ponownie zaktualizowany, tym razem ze starymi użytkownikami i starymi pytaniami. Wynik byłby również widoczny dla użytkowników.=> Jest całkiem bezpieczny (nie jestem pewien co do 100%) w użyciu
async
zcomponentDidMount()
metodą. Jestem jej wielkim fanem i do tej pory nie spotkałem się z żadnymi problemami, które przyprawiają mnie o zbyt duży ból głowy.źródło
setState()
zawsze wiąże się z niewielkim ryzykiem. Powinniśmy postępować ostrożnie.isFetching: true
wewnątrz stanu komponentu. Użyłem tego tylko z Reduxem, ale przypuszczam, że jest to całkowicie poprawne w przypadku zarządzania stanem tylko do reagowania. Chociaż to tak naprawdę nie rozwiązuje problemu aktualizacji tego samego stanu w innym miejscu w kodzie ...isFetching
rozwiązanie flagowe jest dość powszechne, zwłaszcza gdy chcemy odtworzyć niektóre animacje w interfejsie użytkownika, czekając na odpowiedź zaplecza (isFetching: true
).Aktualizacja z kwietnia 2020 r .: Wydaje się, że problem został rozwiązany w najnowszej wersji React 16.13.1, zobacz ten przykład piaskownicy . Dzięki @abernier za wskazanie tego.
Przeprowadziłem kilka badań i znalazłem jedną ważną różnicę: React nie przetwarza błędów z asynchronicznych metod cyklu życia.
Więc jeśli napiszesz coś takiego:
componentDidMount() { throw new Error('I crashed!'); }
wtedy twój błąd zostanie przechwycony przez granicę błędu i możesz go przetworzyć i wyświetlić wdzięczny komunikat.
Jeśli zmienimy kod w ten sposób:
async componentDidMount() { throw new Error('I crashed!'); }
co jest równoważne z tym:
componentDidMount() { return Promise.reject(new Error('I crashed!')); }
wtedy twój błąd zostanie po cichu połknięty . Wstydź się, zareaguj ...
Jak więc przetwarzamy błędy? Jedynym sposobem wydaje się być taki wyraźny haczyk:
async componentDidMount() { try { await myAsyncFunction(); } catch(error) { //... } }
lub tak:
componentDidMount() { myAsyncFunction() .catch(()=> { //... }); }
Jeśli nadal chcemy, aby nasz błąd osiągnął granicę błędu, mogę pomyśleć o następującej sztuczce:
render
metodyPrzykład:
class BuggyComponent extends React.Component { constructor(props) { super(props); this.state = { error: null }; } buggyAsyncfunction(){ return Promise.reject(new Error('I crashed async!'));} async componentDidMount() { try { await this.buggyAsyncfunction(); } catch(error) { this.setState({error: error}); } } render() { if(this.state.error) throw this.state.error; return <h1>I am OK</h1>; } }
źródło
Twój kod jest w porządku i bardzo czytelny dla mnie. Zobacz artykuł Dale'a Jeffersona, w którym pokazuje
componentDidMount
przykład asynchroniczny i również wygląda naprawdę dobrze.Ale niektórzy powiedzieliby, że osoba czytająca kod może założyć, że React robi coś ze zwróconą obietnicą.
Zatem interpretacja tego kodu i czy jest to dobra praktyka, czy nie, jest bardzo osobista.
Jeśli chcesz innego rozwiązania, możesz skorzystać z obietnic . Na przykład:
componentDidMount() { fetch(this.getAuth()) .then(auth => { if (auth) this.checkAuth(auth) }) }
źródło
async
funkcji inline zawait
s wewnątrz ...?(async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()
gdziefetch
isubmitRequest
są funkcje, które zwracają obietnice.Kiedy używasz
componentDidMount
bezasync
słowa kluczowego, dokument mówi tak:Jeśli go użyjesz
async componentDidMount
, utracisz tę zdolność: kolejny render nastąpi PO zaktualizowaniu ekranu przez przeglądarkę. Ale imo, jeśli myślisz o użyciu async, takim jak pobieranie danych, nie możesz uniknąć, że przeglądarka dwukrotnie zaktualizuje ekran. W innym świecie nie jest możliwe wstrzymanie komponentu componentDidMount przed aktualizacją ekranu przez przeglądarkęźródło
Aktualizacja:
(Moja kompilacja: React 16, Webpack 4, Babel 7):
Używając Babel 7, odkryjesz:
Używając tego wzoru ...
async componentDidMount() { try { const res = await fetch(config.discover.url); const data = await res.json(); console.log(data); } catch(e) { console.error(e); } }
napotkasz następujący błąd ...
Uncaught ReferenceError: regeneratorRuntime nie jest zdefiniowany
W takim przypadku będziesz musiał zainstalować babel-plugin-transform-runtime
https://babeljs.io/docs/en/babel-plugin-transform-runtime.html
Jeśli z jakiegoś powodu nie chcesz instalować powyższego pakietu (babel-plugin-transform-runtime), będziesz chciał trzymać się wzorca Promise ...
componentDidMount() { fetch(config.discover.url) .then(res => res.json()) .then(data => { console.log(data); }) .catch(err => console.error(err)); }
źródło
Myślę, że to w porządku, o ile wiesz, co robisz. Ale może to być mylące, ponieważ
async componentDidMount()
może nadal działaćcomponentWillUnmount
uruchomieniu, a komponent został odmontowany.Możesz także chcieć uruchomić w nim zadania synchroniczne i asynchroniczne
componentDidMount
. GdybycomponentDidMount
był asynchroniczny, musiałbyś umieścić cały kod synchroniczny przed pierwszymawait
. Dla kogoś może nie być oczywiste, że kod przed pierwszymawait
działa synchronicznie. W takim przypadku pewnie bym zachowałcomponentDidMount
synchronizację, ale wywoływałbym metody sync i async.Niezależnie od tego, czy wybierzesz metody wywoływania w
async componentDidMount()
porównaniu z synchronizacją , musisz upewnić się, że wyczyściłeś wszystkie detektory lub metody asynchroniczne, które mogą nadal działać po odłączeniu składnika.componentDidMount()
async
źródło
W rzeczywistości ładowanie asynchroniczne w ComponentDidMount jest zalecanym wzorcem projektowym, ponieważ React odchodzi od starszych metod cyklu życia (componentWillMount, componentWillReceiveProps, componentWillUpdate) i przechodzi do renderowania asynchronicznego.
Ten wpis na blogu jest bardzo pomocny w wyjaśnianiu, dlaczego jest to bezpieczne i dostarcza przykładów ładowania asynchronicznego w ComponentDidMount:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
źródło
Lubię używać czegoś takiego
componentDidMount(){ const result = makeResquest() } async makeRequest(){ const res = await fetch(url); const data = await res.json(); return data }
źródło