Apakah Bereaksi menjaga urutan pembaruan negara?

139

Saya tahu bahwa Bereaksi dapat melakukan pembaruan status secara tidak sinkron dan sekaligus untuk optimasi kinerja. Karena itu Anda tidak pernah bisa mempercayai keadaan yang akan diperbarui setelah menelepon setState. Tapi yang dapat Anda percaya Bereaksi untuk memperbarui negara dalam urutan yang sama seperti setStateyang disebut untuk

  1. komponen yang sama?
  2. komponen yang berbeda?

Pertimbangkan mengklik tombol dalam contoh berikut:

1. Apakah ada kemungkinan bahwa a salah dan b benar untuk:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Adakah kemungkinan bahwa a salah dan b benar untuk:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Perlu diingat bahwa ini adalah penyederhanaan ekstrim dari use case saya. Saya menyadari bahwa saya dapat melakukan ini secara berbeda, misalnya memperbarui kedua param negara bagian pada saat yang sama dalam contoh 1, serta melakukan pembaruan keadaan kedua dalam panggilan balik ke pembaruan keadaan pertama dalam contoh 2. Namun, ini bukan pertanyaan saya, dan saya hanya tertarik jika ada cara yang terdefinisi dengan baik bahwa React melakukan pembaruan status ini, tidak ada yang lain.

Setiap jawaban yang didukung oleh dokumentasi sangat dihargai.

darksmurf
sumber
3
sepertinya bukan pertanyaan yang tidak masuk akal, Anda juga dapat mengajukan pertanyaan itu pada masalah github pada halaman reaksi, dan abramov biasanya cukup membantu di sana. Ketika saya memiliki pertanyaan rumit seperti itu, saya akan bertanya dan dia akan menjawab. Yang buruk adalah bahwa masalah-masalah semacam itu tidak dibagikan secara publik seperti dalam dokumen resmi (sehingga orang lain juga dapat mengaksesnya dengan mudah). Saya juga merasa Bereaksi dokumen resmi tidak memiliki cakupan luas dari beberapa topik seperti topik dari pertanyaan Anda, dll.
giorgim
Sebagai contoh, ambil ini: github.com/facebook/react/issues/11793 , saya percaya hal-hal yang dibahas dalam masalah itu akan sangat membantu bagi banyak pengembang tetapi hal-hal itu tidak ada dalam dokumen resmi, karena orang-orang FB menganggap itu maju. Sama mungkin tentang hal-hal lain. Saya akan berpikir artikel resmi berjudul sesuatu seperti 'manajemen negara bereaksi mendalam' atau 'perangkap manajemen negara' yang mengeksplorasi semua kasus sudut manajemen negara seperti dalam pertanyaan Anda tidak akan buruk. mungkin kita bisa mendorong pengembang FB untuk memperpanjang dokumentasi dengan hal-hal seperti itu :)
giorgim
Ada tautan ke artikel bagus tentang sedang dalam pertanyaan saya. Ini harus mencakup 95% dari kasus penggunaan negara. :)
Michal
2
@ Michael tetapi artikel itu masih tidak menjawab pertanyaan ini IMHO
giorgim

Jawaban:

335

Saya mengerjakan React.

TLDR:

Tapi bisakah Anda percaya Bereaksi untuk memperbarui keadaan dalam urutan yang sama seperti setState dipanggil untuk

  • komponen yang sama?

Iya.

  • komponen yang berbeda?

Iya.

The rangka update selalu dihormati. Apakah Anda melihat kondisi antara "di antara" mereka atau tidak tergantung pada apakah Anda berada di dalam kumpulan atau tidak.

Saat ini (Bereaksi 16 dan sebelumnya), hanya pembaruan di dalam penangan aktivitas Bereaksi yang di-batch secara default . Ada API yang tidak stabil untuk memaksa batching di luar event handler untuk kasus yang jarang terjadi saat Anda membutuhkannya.

Dalam versi yang akan datang (mungkin Bereaksi 17 dan yang lebih baru), Bereaksi akan mengelompokkan semua pembaruan secara default sehingga Anda tidak perlu memikirkan hal ini. Seperti biasa, kami akan mengumumkan perubahan apa pun tentang ini di blog Bereaksi dan dalam catatan rilis.


Kunci untuk memahami ini adalah bahwa tidak peduli berapa banyak setState()panggilan dalam berapa banyak komponen yang Anda lakukan di dalam penangan peristiwa Bereaksi , mereka hanya akan menghasilkan render tunggal pada akhir acara . Ini sangat penting untuk kinerja yang baik dalam aplikasi besar karena jika Childdan Parentsetiap panggilan setState()saat menangani acara klik, Anda tidak ingin merender ulang Childdua kali.

Dalam kedua contoh Anda, setState()panggilan terjadi di dalam pengendali event Bereaksi. Oleh karena itu mereka selalu memerah bersama di akhir acara (dan Anda tidak melihat negara perantara).

Pembaruan selalu dangkal digabung dalam urutan terjadinya . Jadi, jika pembaruan pertama adalah {a: 10}, yang kedua adalah {b: 20}, dan yang ketiga adalah {a: 30}, keadaan yang diberikan akan {a: 30, b: 20}. Pembaruan yang lebih baru untuk kunci negara yang sama (misalnya seperti adalam contoh saya) selalu "menang".

The this.stateobjek diperbarui ketika kita kembali membuat UI pada akhir bets. Jadi, jika Anda perlu memperbarui negara berdasarkan keadaan sebelumnya (seperti menambah penghitung), Anda harus menggunakan setState(fn)versi fungsional yang memberi Anda keadaan sebelumnya, alih-alih membaca dari this.state. Jika Anda penasaran dengan alasan untuk ini, saya jelaskan secara mendalam dalam komentar ini .


Dalam contoh Anda, kami tidak akan melihat "keadaan perantara" karena kami berada di dalam penangan peristiwa Bereaksi tempat batching diaktifkan (karena Bereaksi "tahu" ketika kita keluar dari peristiwa itu).

Namun, baik dalam Bereaksi 16 dan versi sebelumnya, belum ada batching secara default di luar penangan event Bereaksi . Jadi, jika dalam contoh Anda, kami memiliki penangan respons AJAX alih-alih handleClick, masing setState()- masing akan segera diproses begitu terjadi. Dalam hal ini, ya, Anda akan melihat kondisi perantara:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Kami menyadari tidak nyaman bahwa perilakunya berbeda tergantung pada apakah Anda berada dalam event handler atau tidak . Ini akan berubah di versi Bereaksi masa depan yang akan mengelompokkan semua pembaruan secara default (dan menyediakan API keikutsertaan untuk menyiram perubahan secara sinkron). Sampai kami beralih perilaku default (berpotensi dalam Bereaksi 17), ada API yang dapat Anda gunakan untuk memaksa batching :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

React event handler internal semuanya dibungkus unstable_batchedUpdatesdan itulah sebabnya mereka dikelompokkan secara default. Perhatikan bahwa membungkus pembaruan unstable_batchedUpdatesdua kali tidak berpengaruh. Pembaruan memerah ketika kita keluar dari unstable_batchedUpdatespanggilan terluar .

API itu "tidak stabil" dalam arti bahwa kami akan menghapusnya ketika batching sudah diaktifkan secara default. Namun, kami tidak akan menghapusnya dalam versi kecil, sehingga Anda dapat dengan aman mengandalkannya sampai Bereaksi 17 jika Anda perlu memaksa batching dalam beberapa kasus di luar React event handler.


Singkatnya, ini adalah topik yang membingungkan karena Bereaksi hanya kumpulan di dalam penangan acara secara default. Ini akan berubah di versi masa depan, dan perilaku akan lebih mudah. Tapi solusinya bukan batch lebih sedikit , itu untuk batch lebih banyak secara default. Itu yang akan kita lakukan.

Dan Abramov
sumber
1
Salah satu cara untuk "selalu mendapatkan urutan yang benar" adalah dengan membuat objek sementara, tetapkan nilai yang berbeda (misalnya obj.a = true; obj.b = true) dan kemudian pada akhirnya lakukan saja this.setState(obj). Ini aman terlepas dari apakah Anda berada di dalam pengendali acara atau tidak. Mungkin trik yang rapi jika Anda sering membuat kesalahan dengan mengatur keadaan beberapa kali di luar event handler.
Chris
Jadi kita tidak bisa benar-benar mengandalkan batching untuk dibatasi hanya pada satu event handler - seperti yang Anda jelaskan, setidaknya karena ini tidak akan menjadi kasus segera. Maka kita seharusnya menggunakan setState dengan fungsi updater untuk mendapatkan akses ke status paling baru, kan? Tetapi bagaimana jika saya perlu menggunakan beberapa state.filter untuk membuat XHR untuk membaca beberapa data dan kemudian memasukkannya ke dalam state? Sepertinya saya harus meletakkan XHR dengan callback yang ditangguhkan (dan karenanya efek samping) ke dalam pembaru. Apakah itu dianggap praktik terbaik?
Maksim Gumerov
1
Dan omong-omong itu juga berarti kita seharusnya tidak membaca dari ini. Keadaan sama sekali; satu-satunya cara yang masuk akal untuk membaca beberapa state.X adalah membacanya dalam fungsi updater, dari argumennya. Dan, menulis ke status ini juga tidak aman. Lalu mengapa mengizinkan akses ke status ini? Mereka mungkin seharusnya membuat pertanyaan mandiri tetapi kebanyakan saya hanya mencoba untuk memahami jika saya mendapatkan penjelasan yang benar.
Maksim Gumerov
10
jawaban ini harus ditambahkan ke dokumentasi reactjs.org
Deen John
2
Bisakah Anda mengklarifikasi dalam posting ini jika "React event handler" termasuk componentDidUpdatedan callback daur hidup lainnya pada React 16? Terima kasih sebelumnya!
Ivan
6

Ini sebenarnya pertanyaan yang cukup menarik tetapi jawabannya tidak boleh terlalu rumit. Ada artikel bagus tentang media yang memiliki jawaban.

1) Jika Anda melakukan ini

this.setState({ a: true });
this.setState({ b: true });

Saya tidak berpikir bahwa akan ada situasi di mana aakan truedan bakan falsekarena batching .

Namun, jika btergantung padaa maka mungkin memang ada situasi di mana Anda tidak akan mendapatkan keadaan yang diharapkan.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

Setelah semua panggilan di atas diproses this.state.valueakan menjadi 1, bukan 3 seperti yang Anda harapkan.

Ini disebutkan dalam artikel: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Ini akan memberi kita this.state.value === 3

Michal
sumber
Bagaimana jika this.state.valuediperbarui baik dalam event handler (di mana setStateadalah batched) dan callback AJAX (mana setStateyang tidak batched). Dalam event handler, saya akan menggunakan updater functionuntuk selalu memastikan bahwa saya memperbarui status menggunakan status yang saat ini diperbarui disediakan oleh fungsi. Haruskah saya gunakan setStatedengan fungsi pembaru di dalam kode panggilan balik AJAX bahkan jika saya tahu itu tidak batched? Bisakah Anda menjelaskan penggunaan setStatecallback AJAX dengan atau tanpa menggunakan fungsi updater? Terima kasih!
tonix
@Michal, hai Michal hanya ingin mengajukan pertanyaan belaka, apakah benar jika kita memiliki this.setState ({value: 0}); this.setState ({value: this.state.value + 1}); setState pertama akan diabaikan dan hanya setState kedua yang akan dieksekusi?
Dickens
@ Ayam Saya percaya keduanya setStateakan dieksekusi tetapi yang terakhir akan menang.
Michal
3

Beberapa panggilan selama siklus yang sama dapat dikumpulkan bersamaan. Misalnya, jika Anda berupaya menambah jumlah item lebih dari satu kali dalam siklus yang sama, itu akan menghasilkan yang setara dengan:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html

Mosè Raguzzini
sumber
3

seperti dalam doc

setState () memberikan perubahan pada status komponen dan memberi tahu Bereaksi bahwa komponen ini dan anak-anaknya perlu dirender ulang dengan keadaan yang diperbarui. Ini adalah metode utama yang Anda gunakan untuk memperbarui antarmuka pengguna sebagai respons terhadap penangan peristiwa dan respons server.

itu akan membentuk sebelumnya perubahan seperti dalam antrian ( FIFO : First In First Out) panggilan pertama akan menjadi yang pertama untuk membentuk sebelumnya

Ali
sumber
hai Ali, hanya ingin mengajukan pertanyaan belaka, apakah benar jika kita memiliki this.setState ({value: 0}); this.setState ({value: this.state.value + 1}); setState pertama akan diabaikan dan hanya setState kedua yang akan dieksekusi?
Dickens