Czy używanie async componentDidMount () jest dobre?


150

Czy używanie componentDidMount()jako funkcji asynchronicznej jest dobrą praktyką w React Native, czy powinienem tego unikać?

Muszę uzyskać informacje od AsyncStoragemomentu 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?


2
„Dobra praktyka” to kwestia opinii. Czy to działa? tak.
Kraylog,

2
Oto dobry artykuł, który pokazuje, dlaczego asynchroniczne oczekiwanie jest dobrą opcją zamiast
Shubham Khatri,

po prostu użyj redux-thunk, to rozwiąże problem
Tilak Maddy

1
@TilakMaddy Dlaczego zakładasz, że każda aplikacja React używa Redux?
Mirakurun

@Mirakurun, dlaczego całe przepełnienie stosu zakładało, że używam jQuery, gdy kiedyś zadawałem proste pytania javascript?
Tilak Maddy

Odpowiedzi:


168

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:

  1. Te asyncsłowa kluczowe: W maszynopisie, jest jedynie markerem kod. Robi 2 rzeczy:
    • Wymuś zwracany typ Promise<void>zamiast void. Jeśli jawnie określisz typ zwracania jako nieobiecujący (np. Void), maszynopis wypluje na ciebie błąd.
    • Umożliwiają użycie awaitsłów kluczowych wewnątrz metody.
  2. Zwracany typ jest zmieniany z voidnaPromise<void>
    • Oznacza to, że możesz teraz zrobić to:
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. Możesz teraz użyć awaitsł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?

  1. Słowo asynckluczowe jest całkowicie nieszkodliwe.
  2. Nie mogę sobie wyobrazić sytuacji, w której trzeba by wywołać componentDidMount()metodę, aby typ zwracany Promise<void>był również nieszkodliwy.

    Wywołanie metody mającej zwracany typ Promise<void>bez awaitsłowa kluczowego nie robi różnicy od wywołania metody mającej zwracany typ void.

  3. 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 asyncz componentDidMount()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.


Kiedy mówisz o problemie, w którym inny setState wystąpił przed oczekującą obietnicą, czy nie jest to to samo z Promise bez cukru składniowego async / await lub nawet klasycznych wywołań zwrotnych?
Clafou,

3
Tak! Opóźnianie setState()zawsze wiąże się z niewielkim ryzykiem. Powinniśmy postępować ostrożnie.
Cù Đức Hiếu

Myślę, że jednym ze sposobów uniknięcia problemów jest użycie czegoś takiego jak isFetching: truewewną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 ...
Clafou

1
Zgadzam się z tym. W rzeczywistości isFetchingrozwiązanie flagowe jest dość powszechne, zwłaszcza gdy chcemy odtworzyć niektóre animacje w interfejsie użytkownika, czekając na odpowiedź zaplecza ( isFetching: true).
Cù Đức Hiếu

3
Możesz napotkać problemy, jeśli ustawisz setState po odmontowaniu komponentu
Eliezer Steinbock

20

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:

  1. Złap błąd, spraw, aby program obsługi błędu zmienił stan komponentu
  2. Jeśli stan wskazuje na błąd, wyrzuć go z rendermetody

Przykł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>;
  }
}

czy jest zgłoszony problem?
Przydałoby

@abernier Myślę, że to racja ... Chociaż prawdopodobnie mogliby to poprawić. Nie zgłosiłem żadnych problemów w tej sprawie ...
CF

1
wydaje się, że już tak nie jest, przynajmniej z React 16.13.1 testowanym tutaj : codeandbox.io/s/bold-ellis-n1cid?
file

10

Twój kod jest w porządku i bardzo czytelny dla mnie. Zobacz artykuł Dale'a Jeffersona, w którym pokazuje componentDidMountprzykł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)
      })
}

3
... lub po prostu użyj asyncfunkcji inline z awaits wewnątrz ...?
Erik Kaplun

również opcja @ErikAllik :)
Tiago Alves

@ErikAllik czy masz przykład?
Pablo Rincon

1
@PabloRincon coś takiego jak (async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()gdzie fetchi submitRequestsą funkcje, które zwracają obietnice.
Erik Kaplun

Ten kod jest zdecydowanie zły, ponieważ połknie każdy błąd, który wystąpił w funkcji getAuth. A jeśli funkcja robi coś z siecią (na przykład), należy spodziewać się błędów.
CF

6

Kiedy używasz componentDidMountbez asyncsłowa kluczowego, dokument mówi tak:

Możesz natychmiast wywołać metodę setState () w module componentDidMount (). Spowoduje to dodatkowe renderowanie, ale nastąpi to zanim przeglądarka zaktualizuje ekran.

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ę


1
Podoba mi się ta odpowiedź, ponieważ jest zwięzła i obsługiwana przez dokumenty. Czy możesz dodać link do dokumentów, do których się odnosisz.
theUtherSide

Może to być nawet dobra rzecz, np. Jeśli wyświetlasz stan ładowania podczas ładowania zasobu, a następnie zawartość, gdy jest zakończona.
Hjulle

3

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));
}

3

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. Gdyby componentDidMountbył asynchroniczny, musiałbyś umieścić cały kod synchroniczny przed pierwszym await. Dla kogoś może nie być oczywiste, że kod przed pierwszym awaitdział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


2

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


3
Renderowanie asynchroniczne w rzeczywistości nie ma nic wspólnego z jawnym asynchronicznym ustawieniem cyklu życia. Właściwie to anty-wzór. Zalecanym rozwiązaniem jest faktycznie wywołanie metody asynchronicznej z metody cyklu życia
Clayton Ray

1

Lubię używać czegoś takiego

componentDidMount(){
   const result = makeResquest()
}
async makeRequest(){
   const res = await fetch(url);
   const data = await res.json();
   return data
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.