Apakah menggunakan async componentDidMount () bagus?

139

Apakah menggunakan componentDidMount()sebagai fungsi async praktik yang baik dalam Bereaksi Asli atau haruskah saya menghindarinya?

Saya perlu mendapatkan beberapa info dari AsyncStoragesaat komponen mount, tetapi satu-satunya cara saya tahu untuk membuat itu mungkin adalah membuat componentDidMount()fungsi async.

async componentDidMount() {
    let auth = await this.getAuth();
    if (auth) 
        this.checkAuth(auth);
}

Apakah ada masalah dengan itu dan apakah ada solusi lain untuk masalah ini?

Mirakurun
sumber
1
"Praktek yang baik" adalah masalah pendapat. Apakah itu bekerja? Iya.
Kraylog
2
Berikut ini adalah artikel bagus yang menunjukkan mengapa menunggu async adalah pilihan yang baik atas janji hackernoon.com/...
Shubham Khatri
cukup gunakan redux-thunk itu akan menyelesaikan masalah
Tilak Maddy
@TilakMaddy Mengapa Anda berasumsi bahwa setiap aplikasi bereaksi menggunakan redux?
Mirakurun
@ Mirakurun mengapa seluruh stack overflow berasumsi bahwa saya menggunakan jQuery ketika saya biasa mengajukan pertanyaan javascript biasa pada hari itu?
Tilak Maddy

Jawaban:

162

Mari kita mulai dengan menunjukkan perbedaan dan menentukan bagaimana hal itu dapat menyebabkan masalah.

Berikut adalah kode componentDidMount()metode siklus hidup async dan "sync" :

// This is typescript code
componentDidMount(): void { /* do something */ }

async componentDidMount(): Promise<void> {
    /* do something */
    /* You can use "await" here */
}

Dengan melihat kode, saya dapat menunjukkan perbedaan berikut:

  1. Kata asynckunci: Dalam naskah, ini hanyalah penanda kode. Itu melakukan 2 hal:
    • Paksa tipe pengembalian menjadi Promise<void>bukan void. Jika Anda secara eksplisit menentukan jenis pengembalian yang tidak menjanjikan (mis: kosong), naskah akan meludahi kesalahan Anda.
    • Memungkinkan Anda menggunakan awaitkata kunci di dalam metode.
  2. Jenis pengembalian diubah dari voidmenjadiPromise<void>
    • Ini berarti Anda sekarang dapat melakukan ini:
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. Anda sekarang dapat menggunakan awaitkata kunci di dalam metode dan untuk sementara menghentikan eksekusi. Seperti ini:

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

Sekarang, bagaimana mereka dapat menyebabkan masalah?

  1. Kata asynckunci tersebut sama sekali tidak berbahaya.
  2. Saya tidak dapat membayangkan situasi apa pun di mana Anda perlu melakukan panggilan ke componentDidMount()metode ini sehingga jenis pengembaliannya Promise<void>juga tidak berbahaya.

    Memanggil ke metode yang memiliki jenis pengembalian Promise<void>tanpa awaitkata kunci tidak akan membuat perbedaan dengan memanggil satu dengan jenis kembali void.

  3. Karena tidak ada metode siklus hidup setelah componentDidMount()menunda pelaksanaannya tampaknya cukup aman. Tetapi ada gotcha.

    Katakanlah, di atas this.setState({users, questions});akan dieksekusi setelah 10 detik. Di tengah waktu tunda, yang lain ...

    this.setState({users: newerUsers, questions: newerQuestions});

    ... berhasil dieksekusi dan DOM dimutakhirkan. Hasilnya terlihat oleh pengguna. Jam terus berdetak dan 10 detik berlalu. Yang tertunda this.setState(...)kemudian akan dijalankan dan DOM akan diperbarui lagi, waktu itu dengan pengguna lama dan pertanyaan lama. Hasilnya juga akan terlihat oleh pengguna.

=> Cukup aman (saya tidak yakin 100%) untuk digunakan asyncdengan componentDidMount()metode. Saya penggemar beratnya dan sejauh ini saya belum menemukan masalah yang membuat saya sakit kepala terlalu banyak.

Cù Đức Hiếu
sumber
Ketika Anda berbicara tentang masalah di mana setState lain terjadi sebelum Janji yang tertunda, bukankah itu sama dengan Janji tanpa async / menunggu gula sintaksis atau bahkan panggilan balik klasik?
Clafou
3
Iya! Tertunda setState()selalu memiliki risiko kecil. Kita harus melanjutkan dengan hati-hati.
Cù Đức Hiếu
Saya kira salah satu cara untuk menghindari masalah adalah dengan menggunakan sesuatu seperti isFetching: truedi dalam keadaan komponen. Saya hanya menggunakan ini dengan redux tapi saya kira itu benar-benar valid dengan manajemen keadaan hanya reaksi. Meskipun itu tidak benar-benar menyelesaikan masalah keadaan yang sama diperbarui di tempat lain dalam kode ...
Clafou
1
Saya setuju dengan itu. Faktanya, isFetchingsolusi flag cukup umum terutama ketika kita ingin memainkan beberapa animasi di front-end sambil menunggu respon back-end ( isFetching: true).
Cù Đức Hiếu
3
Anda dapat mengalami masalah jika Anda melakukan setState setelah komponen di-unmount
Eliezer Steinbock
18

Pembaruan April 2020: Masalah ini tampaknya telah diperbaiki pada React 16.13.1 terbaru, lihat contoh kotak pasir ini . Terima kasih kepada @abernier karena telah menunjukkan ini.


Saya telah melakukan beberapa penelitian, dan saya telah menemukan satu perbedaan penting: Bereaksi tidak memproses kesalahan dari metode siklus hidup async.

Jadi, jika Anda menulis sesuatu seperti ini:

componentDidMount()
{
    throw new Error('I crashed!');
}

maka kesalahan Anda akan ditangkap oleh batas kesalahan , dan Anda dapat memprosesnya dan menampilkan pesan yang anggun.

Jika kita mengubah kode seperti ini:

async componentDidMount()
{
    throw new Error('I crashed!');
}

yang setara dengan ini:

componentDidMount()
{
    return Promise.reject(new Error('I crashed!'));
}

maka kesalahan Anda akan ditelan diam-diam . Malu pada Anda, Bereaksi ...

Jadi, bagaimana kita memproses kesalahan daripada? Satu-satunya cara tampaknya menjadi tangkapan eksplisit seperti ini:

async componentDidMount()
{
    try
    {
         await myAsyncFunction();
    }
    catch(error)
    {
        //...
    }
}

atau seperti ini:

componentDidMount()
{
    myAsyncFunction()
    .catch(()=>
    {
        //...
    });
}

Jika kita masih ingin kesalahan kita mencapai batas kesalahan, saya dapat memikirkan trik berikut:

  1. Tangkap kesalahan, buat penangan kesalahan mengubah status komponen
  2. Jika negara menunjukkan kesalahan, buang itu dari rendermetode

Contoh:

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>;
  }
}
CF
sumber
apakah ada masalah yang dilaporkan untuk ini? Bisa bermanfaat untuk melaporkannya jika masih terjadi ... thx
abernier
@ Saernier Saya pikir ini sesuai dengan peraturan ... Meskipun mungkin mereka bisa memperbaikinya. Saya tidak mengajukan masalah apa pun tentang ini ...
CF
1
tampaknya tidak menjadi masalah lagi, setidaknya dengan Bereaksi 16.13.1 seperti yang diuji di sini: codesandbox.io/s/bold-ellis-n1cid?file=/src/App.js
abernier
9

Kode Anda baik dan sangat mudah dibaca oleh saya. Lihat artikel Dale Jefferson ini di mana ia menunjukkan componentDidMountcontoh async dan terlihat sangat bagus juga.

Tetapi beberapa orang akan mengatakan bahwa seseorang yang membaca kode dapat berasumsi bahwa Bereaksi melakukan sesuatu dengan janji yang dikembalikan.

Jadi interpretasi kode ini dan apakah itu praktik yang baik atau tidak, sangat pribadi.

Jika Anda menginginkan solusi lain, Anda bisa menggunakan janji . Sebagai contoh:

componentDidMount() {
    fetch(this.getAuth())
      .then(auth => {
          if (auth) this.checkAuth(auth)
      })
}
Tiago Alves
sumber
3
... atau juga, cukup gunakan asyncfungsi sebaris dengan awaits di dalam ...?
Erik Kaplun
juga merupakan pilihan @ErikAllik :)
Tiago Alves
@ErikAllik apakah Anda punya contoh?
Pablo Rincon
1
@PabloRincon bertengkar seperti di (async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()mana fetchdan submitRequestadalah fungsi yang mengembalikan janji.
Erik Kaplun
Kode ini benar-benar buruk, karena akan menelan kesalahan yang terjadi pada fungsi getAuth. Dan jika fungsi melakukan sesuatu dengan jaringan (misalnya), kesalahan harus terjadi.
CF
6

Ketika Anda menggunakan componentDidMounttanpa asynckata kunci, dokter mengatakan ini:

Anda dapat memanggil setState () segera di componentDidMount (). Ini akan memicu rendering tambahan, tetapi itu akan terjadi sebelum browser memperbarui layar.

Jika Anda menggunakan async componentDidMountAnda akan kehilangan kemampuan ini: render lain akan terjadi SETELAH browser memperbarui layar. Tetapi saya juga, jika Anda berpikir untuk menggunakan async, seperti mengambil data, Anda tidak dapat menghindari browser akan memperbarui layar dua kali. Di dunia lain, PAUSE componentDidMount tidak dimungkinkan sebelum browser memperbarui layar

Lu Tran
sumber
1
Saya suka jawaban ini karena ringkas dan didukung oleh dokumen. Bisakah Anda menambahkan tautan ke dokumen yang Anda rujuk.
theUtherSide
Ini bahkan mungkin merupakan hal yang baik misalnya jika Anda menampilkan status pemuatan saat sumber daya memuat dan kemudian konten saat selesai.
Hjulle
3

Memperbarui:

(Bangun saya: React 16, Webpack 4, Babel 7):

Saat menggunakan Babel 7 Anda akan menemukan:

Menggunakan pola ini ...

async componentDidMount() {
    try {
        const res = await fetch(config.discover.url);
        const data = await res.json();
        console.log(data);
    } catch(e) {
        console.error(e);
    }
}

Anda akan mengalami kesalahan berikut ...

ReferenceError Uncaught: regeneratorRuntime tidak didefinisikan

Dalam hal ini Anda perlu menginstal babel-plugin-transform-runtime

https://babeljs.io/docs/en/babel-plugin-transform-runtime.html

Jika karena alasan tertentu Anda tidak ingin menginstal paket di atas (babel-plugin-transform-runtime) maka Anda ingin tetap berpegang pada pola Janji ...

componentDidMount() {
    fetch(config.discover.url)
    .then(res => res.json())
    .then(data => {
        console.log(data);
    })
    .catch(err => console.error(err));
}
Chad
sumber
3

Saya pikir itu baik-baik saja selama Anda tahu apa yang Anda lakukan. Tapi itu bisa membingungkan karena async componentDidMount()masih bisa berjalan setelah componentWillUnmountdijalankan dan komponen telah dilepas.

Anda mungkin juga ingin memulai tugas sinkron dan asinkron di dalam componentDidMount. Jika componentDidMountasync, Anda harus meletakkan semua kode sinkron sebelum yang pertama await. Mungkin tidak jelas bagi seseorang bahwa kode sebelum yang pertama awaitberjalan secara sinkron. Dalam hal ini, saya mungkin akan tetap componentDidMountsinkron tetapi minta metode sinkronisasi dan async.

Apakah Anda memilih async componentDidMount()vs metode componentDidMount()panggilan sinkronisasi async, Anda harus memastikan Anda membersihkan setiap pendengar atau metode async yang mungkin masih berjalan ketika komponen dilepas.

dosentmatter
sumber
2

Sebenarnya, memuat async di ComponentDidMount adalah pola desain yang disarankan karena Bereaksi menjauh dari metode siklus hidup sebelumnya (componentWillMount, componentWillReceiveProps, componentWillUpdate) dan selanjutnya ke Render Async.

Posting blog ini sangat membantu dalam menjelaskan mengapa ini aman dan memberikan contoh untuk memuat async di ComponentDidMount:

https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html

DannyMoshe
sumber
3
Render Async sebenarnya tidak ada hubungannya dengan membuat siklus hidup async secara eksplisit. Ini sebenarnya anti-pola. Solusi yang disarankan adalah memanggil metode async dari metode siklus hidup
Clayton Ray
1

Saya suka menggunakan sesuatu seperti ini

componentDidMount(){
   const result = makeResquest()
}
async makeRequest(){
   const res = await fetch(url);
   const data = await res.json();
   return data
}
Gustavo Miguel
sumber