React: RFClarification: mengapa `setState` tidak sinkron?

Dibuat pada 11 Nov 2017  ·  31Komentar  ·  Sumber: facebook/react

Cukup lama saya mencoba memahami mengapa setState tidak sinkron. Dan gagal menemukan jawaban untuk itu di masa lalu, saya sampai pada kesimpulan bahwa itu karena alasan historis dan mungkin sulit untuk diubah sekarang. Namun @gaearon menunjukkan ada alasan yang jelas, jadi saya ingin tahu :)

Ngomong-ngomong, inilah alasan yang sering saya dengar, tetapi saya pikir itu tidak bisa menjadi segalanya karena terlalu mudah untuk dilawan

Async setState diperlukan untuk rendering async

Banyak yang awalnya berpikir itu karena efisiensi render. Tapi saya rasa itu bukan alasan di balik perilaku ini, karena menjaga sinkronisasi setState dengan rendering async terdengar sepele bagi saya, seperti:

Component.prototype.setState = (nextState) => {
  this.state = nextState
  if (!this.renderScheduled)
     setImmediate(this.forceUpdate)
}

Faktanya, misalnya mobx-react memungkinkan penugasan sinkron ke yang dapat diamati dan masih menghormati sifat rendering asinkron

Async setState diperlukan untuk mengetahui status mana yang _dirender_

Argumen lain yang terkadang saya dengar adalah bahwa Anda ingin beralasan tentang status yang _diberikan_, bukan status yang _diminta_. Tetapi saya juga tidak yakin prinsip ini memiliki banyak manfaat. Secara konseptual rasanya aneh bagi saya. Rendering adalah efek samping, negara adalah tentang fakta. Hari ini, saya berusia 32 tahun, dan tahun depan saya akan berusia 33 tahun, terlepas dari apakah komponen pemilik berhasil dirender ulang tahun ini atau tidak :).

Untuk menggambar paralel (mungkin tidak terlalu bagus): Jika Anda tidak dapat _membaca_ versi terakhir dari dokumen kata yang ditulis sendiri sampai Anda mencetaknya, itu akan sangat canggung. Saya tidak berpikir misalnya mesin permainan memberi Anda umpan balik tentang keadaan permainan yang benar-benar dirender dan bingkai mana yang dijatuhkan juga.

Pengamatan yang menarik: Dalam 2 tahun mobx-react tidak ada yang pernah bertanya kepada saya pertanyaan: Bagaimana saya tahu observables saya dirender? Pertanyaan ini sepertinya tidak terlalu relevan.

Saya memang menemukan beberapa kasus di mana mengetahui data mana yang diberikan relevan. Kasus yang saya ingat adalah di mana saya perlu mengetahui dimensi piksel dari beberapa data untuk tujuan tata letak. Tapi itu diselesaikan secara elegan dengan menggunakan didComponentUpdate dan juga tidak terlalu bergantung pada setState menjadi asinkron. Kasus-kasus ini tampak sangat jarang sehingga tidak dapat dibenarkan untuk mendesain api terutama di sekitar mereka. Jika itu bisa dilakukan entah bagaimana , itu sudah cukup saya pikir


Saya tidak ragu bahwa tim React menyadari kebingungan sifat async dari setState sering diperkenalkan, jadi saya menduga ada alasan lain yang sangat bagus untuk semantik saat ini. Ceritakan lebih banyak lagi :)

Discussion

Komentar yang paling membantu

Jadi, inilah beberapa pemikiran. Ini bukan tanggapan yang lengkap dengan cara apa pun, tetapi mungkin ini masih lebih membantu daripada tidak mengatakan apa-apa.

Pertama, saya pikir kami setuju bahwa menunda rekonsiliasi untuk pembaruan batch bermanfaat. Artinya, kami setuju bahwa setState() merender ulang secara serempak akan tidak efisien dalam banyak kasus, dan lebih baik untuk mengelompokkan pembaruan jika kami tahu kami mungkin akan mendapatkan beberapa pembaruan.

Misalnya, jika kita berada di dalam browser click handler, dan keduanya Child dan Parent memanggil setState , kita tidak ingin merender ulang Child dua kali, dan sebagai gantinya lebih memilih untuk menandainya sebagai kotor, dan merender ulang keduanya sebelum keluar dari acara browser.

Anda bertanya: mengapa kita tidak dapat melakukan hal yang sama persis (batching) tetapi menulis pembaruan setState segera ke this.state tanpa menunggu akhir rekonsiliasi. Saya tidak berpikir ada satu jawaban yang jelas (salah satu solusi memiliki pengorbanan) tetapi inilah beberapa alasan yang dapat saya pikirkan.

Menjamin Konsistensi Internal

Bahkan jika state diperbarui secara serempak, props tidak. (Anda tidak dapat mengetahui props sampai Anda merender ulang komponen induk, dan jika Anda melakukannya secara serempak, pengelompokan akan keluar dari jendela.)

Saat ini objek yang disediakan oleh React ( state , props , refs ) secara internal konsisten satu sama lain. Ini berarti bahwa jika Anda hanya menggunakan objek tersebut, objek tersebut dijamin akan merujuk ke pohon yang direkonsiliasi sepenuhnya (bahkan jika itu adalah versi pohon yang lebih lama). Mengapa ini penting?

Saat Anda hanya menggunakan status, jika memerah secara serempak (seperti yang Anda usulkan), pola ini akan berfungsi:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

Namun, katakanlah status ini perlu dicabut untuk dibagikan ke beberapa komponen sehingga Anda memindahkannya ke induk:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Saya ingin menyoroti bahwa dalam aplikasi React biasa yang bergantung pada setState() ini adalah satu-satunya jenis refactoring khusus React yang paling umum yang akan Anda lakukan setiap hari .

Namun, ini merusak kode kami!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

Ini karena, dalam model yang Anda usulkan, this.state akan langsung di-flush tetapi this.props tidak. Dan kita tidak dapat langsung menghapus this.props tanpa merender ulang induknya, yang berarti kita harus menyerah pada batching (yang, tergantung pada kasusnya, dapat menurunkan kinerja secara signifikan).

Ada juga kasus yang lebih halus tentang bagaimana ini bisa pecah, misalnya jika Anda mencampur data dari props (belum di-flush) dan state (diusulkan untuk segera di-flush) untuk membuat status baru : https://github.com/facebook/react/issues/122#issuecomment -81856416. Referensi menyajikan masalah yang sama: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Contoh-contoh ini sama sekali tidak teoretis. Sebenarnya binding React Redux dulu memiliki masalah seperti ini karena mereka mencampur props React dengan status non-React: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/issues/525.

Saya tidak tahu mengapa pengguna MobX belum menemukan ini, tetapi intuisi saya adalah bahwa mereka mungkin menabrak skenario seperti itu tetapi menganggapnya sebagai kesalahan mereka sendiri. Atau mungkin mereka tidak membaca sebanyak props dan malah membaca langsung dari objek MobX yang bisa berubah.

Jadi bagaimana React menyelesaikan ini hari ini? Di React, this.state dan this.props hanya diperbarui setelah rekonsiliasi dan flushing, jadi Anda akan melihat 0 dicetak sebelum dan sesudah refactoring. Ini membuat kondisi pengangkatan menjadi aman.

Ya, ini bisa merepotkan dalam beberapa kasus. Khususnya untuk orang-orang yang berasal dari lebih banyak latar belakang OO yang hanya ingin mengubah status beberapa kali alih-alih memikirkan cara merepresentasikan pembaruan status lengkap di satu tempat. Saya dapat berempati dengan itu, meskipun saya berpikir bahwa menjaga pembaruan status terkonsentrasi lebih jelas dari perspektif debugging: https://github.com/facebook/react/issues/122#issuecomment -19888472 .

Namun, Anda memiliki opsi untuk memindahkan status yang ingin Anda baca segera ke beberapa objek yang dapat diubah ke samping, terutama jika Anda tidak menggunakannya sebagai sumber kebenaran untuk rendering. Itulah yang MobX memungkinkan Anda lakukan .

Anda juga memiliki opsi untuk menyiram seluruh pohon jika Anda tahu apa yang Anda lakukan. API ini disebut ReactDOM.flushSync(fn) . Saya rasa kami belum mendokumentasikannya, tetapi kami pasti akan melakukannya di beberapa titik selama siklus rilis 16.x. Perhatikan bahwa itu benar-benar memaksa rendering ulang lengkap untuk pembaruan yang terjadi di dalam panggilan, jadi Anda harus menggunakannya dengan sangat hemat. Dengan cara ini tidak merusak jaminan konsistensi internal antara props , state , dan refs .

Singkatnya, model React tidak selalu mengarah ke kode yang paling ringkas, tetapi secara internal konsisten dan memastikan status pengangkatan aman .

Mengaktifkan Pembaruan Serentak

Secara konseptual, React berperilaku seolah-olah memiliki antrian pembaruan tunggal per komponen. Inilah sebabnya mengapa diskusi ini masuk akal sama sekali: kami mendiskusikan apakah akan menerapkan pembaruan ke this.state segera atau tidak karena kami tidak ragu pembaruan akan diterapkan dalam urutan yang tepat. Namun, itu tidak perlu terjadi ( haha ).

Baru-baru ini, kita telah banyak berbicara tentang "perenderan asinkron". Saya akui kita belum melakukan pekerjaan yang baik dalam mengomunikasikan apa artinya itu, tetapi itulah sifat R&D: Anda mengejar ide yang tampaknya menjanjikan secara konseptual, tetapi Anda benar-benar memahami implikasinya hanya setelah menghabiskan cukup waktu dengannya.

Salah satu cara kami menjelaskan "async rendering" adalah bahwa React dapat menetapkan prioritas yang berbeda untuk panggilan setState() tergantung dari mana mereka berasal: pengendali acara, respons jaringan, animasi, dll .

Misalnya, jika Anda mengetik pesan, setState() panggilan di TextBox kebutuhan komponen yang akan memerah segera. Namun, jika Anda menerima pesan baru saat mengetik , mungkin lebih baik menunda rendering MessageBubble hingga batas tertentu (misalnya satu detik) daripada membiarkan pengetikan terhenti karena memblokir benang.

Jika kami membiarkan pembaruan tertentu memiliki "prioritas lebih rendah", kami dapat membagi renderingnya menjadi potongan-potongan kecil beberapa milidetik sehingga tidak terlihat oleh pengguna.

Saya tahu pengoptimalan kinerja seperti ini mungkin tidak terdengar sangat menarik atau meyakinkan. Anda bisa mengatakan: "kami tidak membutuhkan ini dengan MobX, pelacakan pembaruan kami cukup cepat untuk menghindari rendering ulang". Saya tidak berpikir itu benar dalam semua kasus (misalnya tidak peduli seberapa cepat MobX, Anda masih harus membuat node DOM dan melakukan rendering untuk tampilan yang baru dipasang). Namun, jika itu benar, dan jika Anda secara sadar memutuskan bahwa Anda baik-baik saja dengan selalu membungkus objek ke dalam pustaka JavaScript tertentu yang melacak membaca dan menulis, mungkin Anda tidak mendapatkan banyak manfaat dari pengoptimalan ini.

Tetapi rendering asinkron bukan hanya tentang pengoptimalan kinerja.

Misalnya, pertimbangkan kasus saat Anda bernavigasi dari satu layar ke layar lainnya. Biasanya Anda akan menampilkan pemintal saat layar baru sedang dirender.

Namun, jika navigasinya cukup cepat (dalam satu atau dua detik), mem-flash dan segera menyembunyikan pemintal menyebabkan pengalaman pengguna yang menurun. Lebih buruk lagi, jika Anda memiliki beberapa level komponen dengan dependensi asinkron yang berbeda (data, kode, gambar), Anda akan berakhir dengan serangkaian pemintal yang berkedip sebentar satu per satu. Ini secara visual tidak menyenangkan dan membuat aplikasi Anda lebih lambat dalam praktiknya karena semua DOM reflow. Ini juga merupakan sumber dari banyak kode boilerplate.

Bukankah lebih baik jika ketika Anda melakukan setState() yang membuat tampilan berbeda, kita bisa "mulai" merender tampilan yang diperbarui "di latar belakang"? Bayangkan bahwa tanpa menulis sendiri kode koordinasi apa pun, Anda dapat memilih untuk menampilkan spinner jika pembaruan membutuhkan waktu lebih dari ambang tertentu (misalnya satu detik), dan jika tidak, biarkan React melakukan transisi mulus ketika dependensi asinkron dari seluruh subpohon baru puas . Selain itu, saat kita "menunggu", "layar lama" tetap interaktif (misalnya, sehingga Anda dapat memilih item yang berbeda untuk transisi), dan React memberlakukan bahwa jika terlalu lama, Anda harus menunjukkan pemintal.

Ternyata, dengan model React saat ini dan beberapa penyesuaian pada siklus hidup , kami benar-benar dapat mengimplementasikan ini! @acdlite telah mengerjakan fitur ini selama beberapa minggu terakhir, dan akan segera memposting RFC untuk fitur ini.

Perhatikan bahwa ini hanya mungkin karena this.state tidak langsung di-flush. Jika langsung di-flush, kami tidak akan memiliki cara untuk mulai merender tampilan "versi baru" di latar belakang sementara "versi lama" masih terlihat dan interaktif. Pembaruan negara independen mereka akan berbenturan.

Saya tidak ingin mencuri guntur dari @acdlite sehubungan dengan mengumumkan semua ini, tetapi saya harap ini terdengar setidaknya sedikit menarik. Saya mengerti ini mungkin masih terdengar seperti perangkat uap, atau seperti kita tidak benar-benar tahu apa yang kita lakukan. Saya harap kami dapat meyakinkan Anda sebaliknya dalam beberapa bulan mendatang, dan Anda akan menghargai fleksibilitas model React. Dan sejauh yang saya pahami, setidaknya sebagian fleksibilitas ini dimungkinkan berkat tidak segera mem

Semua 31 komentar

Kami semua menunggu @gaearon .

@Kaybarax Hei, ini akhir pekan ;-)

@mweststrate Oh! salahku. Dingin.

Saya akan mengambil risiko di sini dan mengatakan itu karena mengelompokkan beberapa setState s di centang yang sama.

Saya akan berlibur minggu depan, tetapi saya mungkin akan pergi pada hari Selasa, jadi saya akan mencoba untuk membalas pada hari Senin.

fungsi enqueueUpdate(komponen) {
sureInjected();

// Berbagai bagian dari kode kita (seperti ReactCompositeComponent's
// _renderValidatedComponent) menganggap bahwa panggilan ke render tidak bersarang;
// verifikasi bahwa itu masalahnya. (Ini disebut oleh setiap pembaruan tingkat atas
// fungsi, seperti setState, forceUpdate, dll.; penciptaan dan
// penghancuran komponen tingkat atas dijaga di ReactMount.)

if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, komponen);
kembali;
}

kotorComponents.push(komponen);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}

@mweststrate hanya 2 sen: itu pertanyaan yang sangat valid.
Saya yakin kita semua setuju bahwa akan lebih mudah untuk bernalar tentang status jika setState sinkron.
Apa pun alasan untuk membuat setState asinkron, saya tidak yakin tim bereaksi dengan baik dibandingkan dengan kelemahan yang akan muncul, misalnya kesulitan untuk mempertimbangkan keadaan sekarang dan kebingungan yang dibawa ke pengembang.

@mweststrate menariknya saya mengajukan pertanyaan yang sama di sini: https://discuss.reactjs.org/t/historic-reasons-behind-setstate-not-being-immediately-visible/8487

Saya pribadi telah dan melihat kebingungan pengembang lain tentang hal ini. @gaearon akan sangat bagus untuk mendapatkan penjelasan untuk ini ketika Anda punya waktu :)

Maaf, ini akhir tahun dan kami agak ketinggalan di GitHub dll mencoba menyelesaikan semua yang telah kami kerjakan sebelum liburan.

Saya berniat untuk kembali ke utas ini dan mendiskusikannya. Tapi itu juga sedikit target bergerak karena kami sedang mengerjakan fitur React async yang secara langsung berhubungan dengan bagaimana dan kapan this.state diperbarui. Saya tidak ingin menghabiskan banyak waktu untuk menulis sesuatu, dan kemudian harus menulis ulang karena asumsi yang mendasarinya telah berubah. Jadi saya ingin tetap membuka ini, tetapi saya belum tahu kapan saya bisa memberikan jawaban yang pasti.

Jadi, inilah beberapa pemikiran. Ini bukan tanggapan yang lengkap dengan cara apa pun, tetapi mungkin ini masih lebih membantu daripada tidak mengatakan apa-apa.

Pertama, saya pikir kami setuju bahwa menunda rekonsiliasi untuk pembaruan batch bermanfaat. Artinya, kami setuju bahwa setState() merender ulang secara serempak akan tidak efisien dalam banyak kasus, dan lebih baik untuk mengelompokkan pembaruan jika kami tahu kami mungkin akan mendapatkan beberapa pembaruan.

Misalnya, jika kita berada di dalam browser click handler, dan keduanya Child dan Parent memanggil setState , kita tidak ingin merender ulang Child dua kali, dan sebagai gantinya lebih memilih untuk menandainya sebagai kotor, dan merender ulang keduanya sebelum keluar dari acara browser.

Anda bertanya: mengapa kita tidak dapat melakukan hal yang sama persis (batching) tetapi menulis pembaruan setState segera ke this.state tanpa menunggu akhir rekonsiliasi. Saya tidak berpikir ada satu jawaban yang jelas (salah satu solusi memiliki pengorbanan) tetapi inilah beberapa alasan yang dapat saya pikirkan.

Menjamin Konsistensi Internal

Bahkan jika state diperbarui secara serempak, props tidak. (Anda tidak dapat mengetahui props sampai Anda merender ulang komponen induk, dan jika Anda melakukannya secara serempak, pengelompokan akan keluar dari jendela.)

Saat ini objek yang disediakan oleh React ( state , props , refs ) secara internal konsisten satu sama lain. Ini berarti bahwa jika Anda hanya menggunakan objek tersebut, objek tersebut dijamin akan merujuk ke pohon yang direkonsiliasi sepenuhnya (bahkan jika itu adalah versi pohon yang lebih lama). Mengapa ini penting?

Saat Anda hanya menggunakan status, jika memerah secara serempak (seperti yang Anda usulkan), pola ini akan berfungsi:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

Namun, katakanlah status ini perlu dicabut untuk dibagikan ke beberapa komponen sehingga Anda memindahkannya ke induk:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Saya ingin menyoroti bahwa dalam aplikasi React biasa yang bergantung pada setState() ini adalah satu-satunya jenis refactoring khusus React yang paling umum yang akan Anda lakukan setiap hari .

Namun, ini merusak kode kami!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

Ini karena, dalam model yang Anda usulkan, this.state akan langsung di-flush tetapi this.props tidak. Dan kita tidak dapat langsung menghapus this.props tanpa merender ulang induknya, yang berarti kita harus menyerah pada batching (yang, tergantung pada kasusnya, dapat menurunkan kinerja secara signifikan).

Ada juga kasus yang lebih halus tentang bagaimana ini bisa pecah, misalnya jika Anda mencampur data dari props (belum di-flush) dan state (diusulkan untuk segera di-flush) untuk membuat status baru : https://github.com/facebook/react/issues/122#issuecomment -81856416. Referensi menyajikan masalah yang sama: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Contoh-contoh ini sama sekali tidak teoretis. Sebenarnya binding React Redux dulu memiliki masalah seperti ini karena mereka mencampur props React dengan status non-React: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/issues/525.

Saya tidak tahu mengapa pengguna MobX belum menemukan ini, tetapi intuisi saya adalah bahwa mereka mungkin menabrak skenario seperti itu tetapi menganggapnya sebagai kesalahan mereka sendiri. Atau mungkin mereka tidak membaca sebanyak props dan malah membaca langsung dari objek MobX yang bisa berubah.

Jadi bagaimana React menyelesaikan ini hari ini? Di React, this.state dan this.props hanya diperbarui setelah rekonsiliasi dan flushing, jadi Anda akan melihat 0 dicetak sebelum dan sesudah refactoring. Ini membuat kondisi pengangkatan menjadi aman.

Ya, ini bisa merepotkan dalam beberapa kasus. Khususnya untuk orang-orang yang berasal dari lebih banyak latar belakang OO yang hanya ingin mengubah status beberapa kali alih-alih memikirkan cara merepresentasikan pembaruan status lengkap di satu tempat. Saya dapat berempati dengan itu, meskipun saya berpikir bahwa menjaga pembaruan status terkonsentrasi lebih jelas dari perspektif debugging: https://github.com/facebook/react/issues/122#issuecomment -19888472 .

Namun, Anda memiliki opsi untuk memindahkan status yang ingin Anda baca segera ke beberapa objek yang dapat diubah ke samping, terutama jika Anda tidak menggunakannya sebagai sumber kebenaran untuk rendering. Itulah yang MobX memungkinkan Anda lakukan .

Anda juga memiliki opsi untuk menyiram seluruh pohon jika Anda tahu apa yang Anda lakukan. API ini disebut ReactDOM.flushSync(fn) . Saya rasa kami belum mendokumentasikannya, tetapi kami pasti akan melakukannya di beberapa titik selama siklus rilis 16.x. Perhatikan bahwa itu benar-benar memaksa rendering ulang lengkap untuk pembaruan yang terjadi di dalam panggilan, jadi Anda harus menggunakannya dengan sangat hemat. Dengan cara ini tidak merusak jaminan konsistensi internal antara props , state , dan refs .

Singkatnya, model React tidak selalu mengarah ke kode yang paling ringkas, tetapi secara internal konsisten dan memastikan status pengangkatan aman .

Mengaktifkan Pembaruan Serentak

Secara konseptual, React berperilaku seolah-olah memiliki antrian pembaruan tunggal per komponen. Inilah sebabnya mengapa diskusi ini masuk akal sama sekali: kami mendiskusikan apakah akan menerapkan pembaruan ke this.state segera atau tidak karena kami tidak ragu pembaruan akan diterapkan dalam urutan yang tepat. Namun, itu tidak perlu terjadi ( haha ).

Baru-baru ini, kita telah banyak berbicara tentang "perenderan asinkron". Saya akui kita belum melakukan pekerjaan yang baik dalam mengomunikasikan apa artinya itu, tetapi itulah sifat R&D: Anda mengejar ide yang tampaknya menjanjikan secara konseptual, tetapi Anda benar-benar memahami implikasinya hanya setelah menghabiskan cukup waktu dengannya.

Salah satu cara kami menjelaskan "async rendering" adalah bahwa React dapat menetapkan prioritas yang berbeda untuk panggilan setState() tergantung dari mana mereka berasal: pengendali acara, respons jaringan, animasi, dll .

Misalnya, jika Anda mengetik pesan, setState() panggilan di TextBox kebutuhan komponen yang akan memerah segera. Namun, jika Anda menerima pesan baru saat mengetik , mungkin lebih baik menunda rendering MessageBubble hingga batas tertentu (misalnya satu detik) daripada membiarkan pengetikan terhenti karena memblokir benang.

Jika kami membiarkan pembaruan tertentu memiliki "prioritas lebih rendah", kami dapat membagi renderingnya menjadi potongan-potongan kecil beberapa milidetik sehingga tidak terlihat oleh pengguna.

Saya tahu pengoptimalan kinerja seperti ini mungkin tidak terdengar sangat menarik atau meyakinkan. Anda bisa mengatakan: "kami tidak membutuhkan ini dengan MobX, pelacakan pembaruan kami cukup cepat untuk menghindari rendering ulang". Saya tidak berpikir itu benar dalam semua kasus (misalnya tidak peduli seberapa cepat MobX, Anda masih harus membuat node DOM dan melakukan rendering untuk tampilan yang baru dipasang). Namun, jika itu benar, dan jika Anda secara sadar memutuskan bahwa Anda baik-baik saja dengan selalu membungkus objek ke dalam pustaka JavaScript tertentu yang melacak membaca dan menulis, mungkin Anda tidak mendapatkan banyak manfaat dari pengoptimalan ini.

Tetapi rendering asinkron bukan hanya tentang pengoptimalan kinerja.

Misalnya, pertimbangkan kasus saat Anda bernavigasi dari satu layar ke layar lainnya. Biasanya Anda akan menampilkan pemintal saat layar baru sedang dirender.

Namun, jika navigasinya cukup cepat (dalam satu atau dua detik), mem-flash dan segera menyembunyikan pemintal menyebabkan pengalaman pengguna yang menurun. Lebih buruk lagi, jika Anda memiliki beberapa level komponen dengan dependensi asinkron yang berbeda (data, kode, gambar), Anda akan berakhir dengan serangkaian pemintal yang berkedip sebentar satu per satu. Ini secara visual tidak menyenangkan dan membuat aplikasi Anda lebih lambat dalam praktiknya karena semua DOM reflow. Ini juga merupakan sumber dari banyak kode boilerplate.

Bukankah lebih baik jika ketika Anda melakukan setState() yang membuat tampilan berbeda, kita bisa "mulai" merender tampilan yang diperbarui "di latar belakang"? Bayangkan bahwa tanpa menulis sendiri kode koordinasi apa pun, Anda dapat memilih untuk menampilkan spinner jika pembaruan membutuhkan waktu lebih dari ambang tertentu (misalnya satu detik), dan jika tidak, biarkan React melakukan transisi mulus ketika dependensi asinkron dari seluruh subpohon baru puas . Selain itu, saat kita "menunggu", "layar lama" tetap interaktif (misalnya, sehingga Anda dapat memilih item yang berbeda untuk transisi), dan React memberlakukan bahwa jika terlalu lama, Anda harus menunjukkan pemintal.

Ternyata, dengan model React saat ini dan beberapa penyesuaian pada siklus hidup , kami benar-benar dapat mengimplementasikan ini! @acdlite telah mengerjakan fitur ini selama beberapa minggu terakhir, dan akan segera memposting RFC untuk fitur ini.

Perhatikan bahwa ini hanya mungkin karena this.state tidak langsung di-flush. Jika langsung di-flush, kami tidak akan memiliki cara untuk mulai merender tampilan "versi baru" di latar belakang sementara "versi lama" masih terlihat dan interaktif. Pembaruan negara independen mereka akan berbenturan.

Saya tidak ingin mencuri guntur dari @acdlite sehubungan dengan mengumumkan semua ini, tetapi saya harap ini terdengar setidaknya sedikit menarik. Saya mengerti ini mungkin masih terdengar seperti perangkat uap, atau seperti kita tidak benar-benar tahu apa yang kita lakukan. Saya harap kami dapat meyakinkan Anda sebaliknya dalam beberapa bulan mendatang, dan Anda akan menghargai fleksibilitas model React. Dan sejauh yang saya pahami, setidaknya sebagian fleksibilitas ini dimungkinkan berkat tidak segera mem

Penjelasan mendalam yang luar biasa untuk keputusan di balik arsitektur React. Terima kasih.

tanda

Terima kasih, Dan.

Saya ❤️ masalah ini. Pertanyaan yang luar biasa dan jawaban yang luar biasa. Saya selalu berpikir bahwa ini adalah keputusan desain yang buruk, sekarang saya harus memikirkan kembali

Terima kasih, Dan.

Saya menyebutnya asyncAwesome setState :smile:

Saya cenderung berpikir bahwa semuanya harus diimplementasikan async terlebih dahulu, dan jika Anda merasa perlu untuk operasi sinkronisasi, bungkus operasi async dengan menunggu selesai. Jauh lebih mudah untuk membuat kode sinkronisasi dari kode async (yang Anda butuhkan hanyalah pembungkus) daripada sebaliknya (yang pada dasarnya membutuhkan penulisan ulang yang lengkap, kecuali Anda meraih threading, yang sama sekali tidak ringan).

@gaearon terima kasih atas penjelasannya yang luas! Itu telah mengganggu saya untuk waktu yang lama ("pasti ada alasan yang bagus, tetapi tidak ada yang tahu yang mana"). Tapi sekarang masuk akal dan saya melihat bagaimana ini adalah keputusan yang benar-benar sadar :). Terima kasih banyak atas jawaban yang luas, sangat menghargainya!

Atau mungkin mereka tidak membaca banyak dari props dan malah membaca langsung dari objek MobX yang bisa berubah.

Saya pikir ini memang benar, di MobX props biasanya digunakan hanya sebagai konfigurasi komponen, dan data domain biasanya tidak ditangkap di props, tetapi di entitas domain yang diedarkan di antara komponen.

Sekali lagi, terima kasih banyak!

@gaearon Terima kasih atas penjelasannya yang detail dan bagus.
Meskipun masih ada sesuatu yang hilang di sini yang saya pikir saya mengerti tetapi ingin memastikan.

Ketika event terdaftar "Outside React", itu berarti mungkin melalui addEventListener pada referensi misalnya. Kemudian tidak ada batching yang terjadi.
Pertimbangkan kode ini:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

Ketika kita akan mengklik tombol "React Event", kita akan melihat di konsol:
"before setState" 0
"after setState" 0
Ketika tombol lain "Acara DOM langsung" akan diklik, kita akan melihat di konsol:
"before setState" 0
"after setState" 1

Setelah beberapa penelitian kecil dan browsing melalui kode sumber, saya rasa saya tahu mengapa ini terjadi.
reaksi tidak dapat sepenuhnya mengontrol aliran acara dan tidak dapat memastikan kapan dan bagaimana acara berikutnya akan diaktifkan, sehingga sebagai "mode panik" itu hanya akan memicu perubahan status dengan segera.

Apa pendapat anda tentang ini? :berpikir:

@sag1v meskipun sedikit terkait, mungkin lebih jelas untuk membuka masalah baru untuk pertanyaan baru. Cukup gunakan #11527 di suatu tempat di deskripsi untuk menautkannya ke yang ini.

@sag1v @gaearon telah memberi saya balasan yang sangat singkat di sini https://twitter.com/dan_abramov/status/949992957180104704 . Saya pikir pendapatnya tentang ini juga akan menjawab saya dengan lebih konkret.

@mweststrate Saya berpikir untuk membuka masalah baru tetapi kemudian saya menyadari bahwa ini terkait langsung dengan pertanyaan Anda "mengapa setState sinkron?".
Karena diskusi ini adalah tentang keputusan yang dibuat untuk membuat setState "async", saya pikir untuk menambahkan kapan dan mengapa membuatnya "sinkron".
Saya tidak keberatan membuka masalah baru jika saya tidak meyakinkan Anda bahwa posting saya terkait dengan masalah ini :wink:

@Kaybarax Itu karena pertanyaan Anda adalah "_Kapan sinkronisasi_" dan bukan "_ Mengapa sinkronisasi_"?.
Seperti yang saya sebutkan di posting saya, saya pikir saya tahu alasannya, tetapi saya ingin memastikan dan mendapatkan jawaban Resmi hehe. :senyum:

reaksi tidak dapat sepenuhnya mengontrol aliran acara dan tidak dapat memastikan kapan dan bagaimana acara berikutnya akan diaktifkan, sehingga sebagai "mode panik" itu hanya akan memicu perubahan status segera

semacam. Meskipun ini tidak sepenuhnya terkait dengan pertanyaan tentang memperbarui this.state .

Yang Anda tanyakan adalah dalam kasus apa React mengaktifkan batching. React saat ini mengelompokkan pembaruan di dalam event handler yang dikelola oleh React karena React “duduk” di bingkai tumpukan panggilan teratas dan mengetahui kapan semua event handler React telah berjalan. Pada saat itu mem-flush pembaruan.

Jika event handler tidak diatur oleh React, saat ini ia membuat pembaruan sinkron. Karena tidak tahu apakah aman untuk menunggu atau tidak, dan apakah pembaruan lainnya akan segera terjadi.

Di versi React yang akan datang, perilaku ini akan berubah. Rencananya adalah untuk memperlakukan pembaruan sebagai prioritas rendah secara default sehingga mereka akhirnya digabungkan dan digabungkan bersama (misalnya dalam satu detik), dengan memilih untuk segera menghapusnya. Anda dapat membaca lebih lanjut di sini: https://github.com/facebook/react/issues/11171#issuecomment -357945371.

Luar biasa!

Pertanyaan & jawaban itu harus didokumentasikan di tempat yang lebih mudah dijangkau. Terima kasih teman-teman yang mencerahkan kami.

Belajar banyak . Terima kasih

Mencoba menambahkan sudut pandang saya ke utas. Saya bekerja pada aplikasi berbasis MobX beberapa bulan, saya telah menjelajahi ClojureScript selama bertahun-tahun dan membuat alternatif React saya sendiri (disebut Respo), saya mencoba Redux di hari-hari awal meskipun waktu yang sangat singkat, dan saya bertaruh pada ReasonML.

Ide inti dalam menggabungkan React dan Pemrograman Fungsional (FP) adalah Anda mendapatkan sepotong data yang dapat Anda buat menjadi tampilan dengan keterampilan apa pun yang Anda miliki yang mematuhi hukum di FP. Anda tidak mendapat efek samping jika Anda hanya menggunakan fungsi murni.

React tidak murni fungsional. Dengan merangkul status lokal di dalam komponen, React memiliki kekuatan untuk berinteraksi dengan berbagai pustaka yang terkait dengan DOM dan API browser lainnya (juga ramah untuk MobX), yang sementara itu membuat React tidak murni. Namun, saya mencoba di ClojureScript, jika React murni, itu bisa menjadi bencana karena sangat sulit untuk berinteraksi dengan begitu banyak perpustakaan yang ada yang memiliki efek samping di dalamnya.

Jadi di Respo(solusi saya sendiri), saya memiliki dua tujuan yang tampaknya bertentangan, 1) view = f(store) jadi tidak ada keadaan lokal yang diharapkan; 2) Saya tidak suka memprogram semua status UI komponen dalam reduksi global karena itu bisa sulit untuk dipertahankan. Pada akhirnya, saya menemukan bahwa saya memerlukan gula sintaks, yang membantu saya mempertahankan status komponen di toko global dengan paths , sementara itu saya menulis pembaruan status di dalam komponen dengan makro Clojure.

Jadi apa yang saya pelajari: status lokal adalah fitur pengalaman pengembang, di bawahnya kami menginginkan status global yang memungkinkan mesin kami melakukan pengoptimalan secara mendalam. Jadi, orang-orang MobX lebih suka OOP, apakah itu untuk pengalaman pengembang, atau untuk mesin?

Omong-omong, saya berbicara tentang masa depan React dan fitur async-nya, jika Anda melewatkannya:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

Dan, kamu adalah idolaku..... terima kasih banyak.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat