Fungsional setState adalah masa depan React

Pembaruan: Saya memberikan ceramah lanjutan tentang topik ini di React Rally. Sementara posting ini lebih tentang pola "setState fungsional", pembicaraan lebih lanjut tentang memahami setState secara mendalam

React telah mempopulerkan pemrograman fungsional di JavaScript. Hal ini menyebabkan kerangka kerja raksasa yang mengadopsi pola UI berbasis Komponen yang digunakan React. Dan sekarang demam fungsional menyebar ke ekosistem pengembangan web secara luas.

Tapi tim React masih jauh dari menyerah. Mereka terus menggali lebih dalam, menemukan lebih banyak permata fungsional yang tersembunyi di perpustakaan legendaris.

Jadi hari ini saya mengungkapkan kepada Anda emas fungsional baru yang terkubur di React, paling baik dijaga kerahasiaannya React - Functional setState!

Oke, saya baru saja membuat nama itu… dan itu tidak sepenuhnya baru atau rahasia. Tidak, tidak juga. Lihat, ini adalah pola yang dibangun di dalam React, itu hanya diketahui oleh beberapa pengembang yang benar-benar telah menggali lebih dalam. Dan itu tidak pernah memiliki nama. Tapi sekarang berhasil - Fungsional setState!

Mengikuti kata-kata Dan Abramov dalam mendeskripsikan pola ini, Functional setState adalah pola di mana Anda

“Deklarasikan perubahan status secara terpisah dari kelas komponen.”

Hah?

Oke… apa yang sudah kamu ketahui

React adalah pustaka UI berbasis komponen. Komponen pada dasarnya adalah fungsi yang menerima beberapa properti dan mengembalikan elemen UI.

function User(props) { return ( A pretty user );}

Sebuah komponen mungkin perlu memiliki dan mengelola statusnya. Dalam hal ini, Anda biasanya menulis komponen sebagai kelas. Kemudian Anda memiliki statusnya langsung di constructorfungsi kelas :

class User { constructor () { this.state = { score : 0 }; }
 render () { return ( This user scored {this.state.score} ); }}

Untuk mengelola status, React menyediakan metode khusus yang disebut setState(). Anda menggunakannya seperti ini:

class User { ... 
 increaseScore () { this.setState({score : this.state.score + 1}); }
 ...}

Perhatikan cara setState()kerjanya. Anda meneruskannya ke objek yang berisi bagian dari status yang ingin Anda perbarui. Dengan kata lain, objek yang Anda lewati akan memiliki kunci yang sesuai dengan kunci dalam status komponen, kemudian setState()memperbarui atau menyetel status dengan menggabungkan objek ke status. Jadi, "set-State".

Apa yang mungkin tidak Anda ketahui

Ingat bagaimana kami mengatakan setState()bekerja? Nah, bagaimana jika saya memberi tahu Anda bahwa alih-alih mengirimkan objek, Anda bisa meneruskan fungsi ?

Iya. setState()juga menerima suatu fungsi. Fungsi ini menerima status sebelumnya dan props terkini dari komponen yang digunakannya untuk menghitung dan mengembalikan status berikutnya. Lihat di bawah ini:

this.setState(function (state, props) { return { score: state.score - 1 }});

Perhatikan bahwa itu setState()adalah sebuah fungsi, dan kami mengirimkan fungsi lain ke sana (pemrograman fungsional ... setState fungsional ). Pada pandangan pertama, ini mungkin tampak jelek, terlalu banyak langkah untuk menetapkan keadaan. Mengapa Anda ingin melakukan ini?

Mengapa meneruskan fungsi ke setState?

Masalahnya, pembaruan status mungkin tidak sinkron.

Pikirkan tentang apa yang terjadi saat setState()dipanggil. React pertama-tama akan menggabungkan objek yang Anda berikan setState()ke status saat ini. Kemudian rekonsiliasi itu akan dimulai . Ini akan membuat pohon Elemen React baru (representasi objek dari UI Anda), bedakan pohon baru dengan pohon lama, cari tahu apa yang telah berubah berdasarkan objek yang Anda teruskan setState(), lalu terakhir perbarui DOM.

Wah! Banyak pekerjaan! Nyatanya, ini bahkan ringkasan yang terlalu disederhanakan. Tapi percayalah pada React!

React tidak hanya sekedar “set-state”.

Karena banyaknya pekerjaan yang terlibat, menelepon setState()mungkin tidak segera memperbarui status Anda.

React dapat mengumpulkan banyak setState()panggilan menjadi satu pembaruan untuk kinerja.

Apa yang dimaksud React dengan ini?

Pertama, " banyak setState()panggilan" bisa berarti memanggil setState()dalam satu fungsi lebih dari sekali, seperti ini:

...
state = {score : 0};
// multiple setState() callsincreaseScoreBy3 () { this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1});}
...

Sekarang ketika React, menemukan " beberapa setState()panggilan", daripada melakukan "set-state" tiga kali secara terus menerus, React akan menghindari sejumlah besar pekerjaan yang saya jelaskan di atas dan dengan cerdas berkata pada dirinya sendiri: "Tidak! Saya tidak akan mendaki gunung ini tiga kali, membawa dan memperbarui beberapa bagian keadaan di setiap perjalanan. Tidak, saya lebih suka mendapatkan wadah, mengemas semua potongan ini bersama-sama, dan melakukan pembaruan ini hanya sekali. ” Dan itu, teman-teman sayabatching !

Ingatlah bahwa yang Anda berikan setState()adalah benda biasa. Sekarang, asumsikan setiap kali React menemukan " beberapa setState()panggilan", ia melakukan hal batch dengan mengekstrak semua objek yang diteruskan ke setiap setState()panggilan, menggabungkannya bersama untuk membentuk satu objek, lalu menggunakan objek tunggal tersebut untuk melakukannya setState().

Dalam JavaScript, objek penggabungan mungkin terlihat seperti ini:

const singleObject = Object.assign( {}, objectFromSetState1, objectFromSetState2, objectFromSetState3);

Pola ini dikenal sebagai komposisi objek.

Dalam JavaScript, cara kerja "menggabungkan" atau menyusun objek adalah: jika tiga objek memiliki kunci yang sama, nilai kunci objek terakhir yang diteruskan ke Object.assign()menang. Sebagai contoh:

const me = {name : "Justice"}, you = {name : "Your name"}, we = Object.assign({}, me, you);
we.name === "Your name"; //true
console.log(we); // {name : "Your name"}

Because you are the last object merged into we, the value of name in the you object — “Your name” — overrides the value of name in the me object. So “Your name” makes it into the we object… you win! :)

Thus, if you call setState() with an object multiple times — passing an object each time — React will merge. Or in other words, it will compose a new object out of the multiple objects we passed it. And if any of the objects contains the same key, the value of the key of the last object with same key is stored. Right?

Artinya, mengingat increaseScoreBy3fungsi kita di atas, hasil akhir dari fungsi tersebut hanya akan menjadi 1, bukan 3, karena React tidak segera memperbarui status dalam urutan yang kita panggil setState(). Tapi pertama-tama, React menyusun semua objek bersama-sama, yang menghasilkan ini:, {score : this.state.score + 1}kemudian hanya melakukan "set-state" sekali - dengan objek yang baru dibuat. Sesuatu seperti ini: User.setState({score : this.state.score + 1}.

Agar lebih jelas, meneruskan objek ke setState()bukanlah masalah di sini. Masalah sebenarnya adalah meneruskan objek ke setState()saat Anda ingin menghitung keadaan selanjutnya dari keadaan sebelumnya. Jadi berhentilah melakukan ini. Itu tidak aman!

Karena this.propsdan this.statedapat diperbarui secara asinkron, Anda tidak boleh mengandalkan nilainya untuk menghitung status berikutnya.

Ini adalah pena dari Sophia Shoemaker yang menunjukkan masalah ini. Mainkan, dan perhatikan baik solusi buruk dan baik di pena ini:

SetState fungsional untuk menyelamatkan

Jika Anda belum menghabiskan waktu bermain-main dengan pena di atas, saya sangat menyarankan Anda melakukannya, karena ini akan membantu Anda memahami konsep inti dari posting ini.

Saat Anda bermain dengan pena di atas, Anda pasti melihat bahwa setState fungsional memperbaiki masalah kami. Tapi bagaimana tepatnya?

Mari berkonsultasi dengan Oprah of React - Dan.

Perhatikan jawaban yang dia berikan. Saat Anda melakukan setState fungsional…

Pembaruan akan diantrekan dan kemudian dieksekusi sesuai urutan pemanggilannya.

So, when React encounters “multiple functional setState() calls” , instead of merging objects together, (of course there are no objects to merge) React queues the functions “in the order they were called.”

After that, React goes on updating the state by calling each functions in the “queue”, passing them the previous state — that is, the state as it was before the first functional setState() call (if it’s the first functional setState() currently executing) or the state with the latest update from the previous functional setState() call in the queue.

Again, I think seeing some code would be great. This time though, we’re gonna fake everything. Know that this is not the real thing, but is instead just here to give you an idea of what React is doing.

Also, to make it less verbose, we’ll use ES6. You can always write the ES5 version later if you want.

First, let’s create a component class. Then, inside it, we’ll create a fake setState() method. Also, our component would have a increaseScoreBy3()method, which will do a multiple functional setState. Finally, we’ll instantiate the class, just as React would do.

class User{ state = {score : 0};
 //let's fake setState setState(state, callback) { this.state = Object.assign({}, this.state, state); if (callback) callback(); }
 // multiple functional setState call increaseScoreBy3 () { this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ) }}
const Justice = new User();

Note that setState also accepts an optional second parameter — a callback function. If it’s present React calls it after updating the state.

Now when a user triggers increaseScoreBy3(), React queues up the multiple functional setState. We won’t fake that logic here, as our focus is on what actually makes functional setState safe. But you can think of the result of that “queuing” process to be an array of functions, like this:

const updateQueue = [ (state) => ({score : state.score + 1}), (state) => ({score : state.score + 1}), (state) => ({score : state.score + 1})];

Finally, let’s fake the updating process:

// recursively update state in the orderfunction updateState(component, updateQueue) { if (updateQueue.length === 1) { return component.setState(updateQueue[0](component.state)); }
return component.setState( updateQueue[0](component.state), () => updateState( component, updateQueue.slice(1)) );}
updateState(Justice, updateQueue);

True, this is not as so sexy a code. I trust you could do better. But the key focus here is that every time React executes the functions from your functional setState, React updates your state by passing it a fresh copy of the updated state. That makes it possible for functional setState to set state based on the previous state.

Here I made a bin with the complete code. Tinker around it (possibly make it look sexier), just to get more sense of it.

FunctionalSetStateInAction

A Play with the code in this bin will be fun. Remember! we’re just faking React to get the idea...jsbin.com

Play with it to grasp it fully. When you come back we’re gonna see what makes functional setState truly golden.

The best-kept React secret

So far, we’ve deeply explored why it’s safe to do multiple functional setStates in React. But we haven’t actually fulfilled the complete definition of functional setState: “Declare state changes separately from the component classes.”

Over the years, the logic of setting-state — that is, the functions or objects we pass to setState() — have always lived inside the component classes. This is more imperative than declarative.

Well today, I present you with newly unearthed treasure — the best-kept React secret:

Thanks to Dan Abramov!

That is the power of functional setState. Declare your state update logic outside your component class. Then call it inside your component class.

// outside your component classfunction increaseScore (state, props) { return {score : state.score + 1}}
class User{ ...
// inside your component class handleIncreaseScore () { this.setState( increaseScore) }
 ...}

This is declarative! Your component class no longer cares how the state updates. It simply declares the type of update it desires.

To deeply appreciate this, think about those complex components that would usually have many state slices, updating each slice on different actions. And sometimes, each update function would require many lines of code. All of this logic would live inside your component. But not anymore!

Also, if you’re like me, I like keeping every module as short as possible, but now you feel like your module is getting too long. Now you have the power to extract all your state change logic to a different module, then import and use it in your component.

import {increaseScore} from "../stateChanges";
class User{ ...
 // inside your component class handleIncreaseScore () { this.setState( increaseScore) }
 ...}

Now you can even reuse the increaseScore function in a different component. Just import it.

What else can you do with functional setState?

Make testing easy!

You can also pass extra arguments to calculate the next state (this one blew my mind… #funfunFunction).

Expect even more in…

The Future of React

For years now, the react team has been experimenting with how to best implement stateful functions.

Functional setState seems to be just the right answer to that (probably).

Hey, Dan! Any last words?

If you’ve made it this far, you’re probably as excited as I am. Start experimenting with this functional setStatetoday!

If you feel like I’ve done any nice job, or that others deserve a chance to see this, kindly click on the green heart below to help spread a better understanding of React in our community.

If you have a question that hasn’t been answered or you don’t agree with some of the points here feel free to drop in comments here or via Twitter.

Happy Coding!