setState الوظيفية هي مستقبل React

تحديث: لقد قدمت حديثًا متابعة حول هذا الموضوع في React Rally. في حين أن هذا المنشور يتعلق أكثر بنمط "setState الوظيفي" ، فإن الحديث يدور أكثر حول فهم setState بعمق

قامت React بتعميم البرمجة الوظيفية في JavaScript. وقد أدى ذلك إلى وجود أطر عملاقة تتبنى نمط واجهة المستخدم القائم على المكون الذي تستخدمه React. والآن تنتقل الحمى الوظيفية إلى النظام البيئي لتطوير الويب بشكل عام.

لكن فريق React بعيد كل البعد عن التراجع. يواصلون الحفر أعمق ، واكتشاف المزيد من الأحجار الكريمة الوظيفية المخبأة في المكتبة الأسطورية.

لذا أكشِف لكم اليوم عن ذهب وظيفي جديد مدفون في React ، من الأفضل الاحتفاظ بسرية React - مجموعة وظيفية!

حسنًا ، لقد اختلقت هذا الاسم للتو ... وهو ليس جديدًا تمامًا أو سرًا. لا ليس بالضبط. انظر ، إنه نمط مدمج في React ، لا يعرفه سوى عدد قليل من المطورين الذين تعمقوا حقًا. ولم يكن لها اسم. لكن الأمر كذلك الآن - setState الوظيفية!

وفقًا لكلمات دان أبراموف في وصف هذا النمط ، فإن الحالة الوظيفية للمجموعة هي نمط حيث أنت

"الإعلان عن تغييرات الحالة بشكل منفصل عن فئات المكونات."

هاه؟

حسنًا ... ما تعرفه بالفعل

React هي مكتبة واجهة مستخدم قائمة على المكون. المكون هو في الأساس وظيفة تقبل بعض الخصائص وتعيد عنصر واجهة المستخدم.

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

قد يحتاج المكون إلى أن يكون لديه حالته ويديرها. في هذه الحالة ، عادة ما تكتب المكون كفئة. ثم لديك حالتها تعيش في constructorوظيفة الفصل :

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

لإدارة الحالة ، توفر React طريقة خاصة تسمى setState(). تستخدمه مثل هذا:

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

لاحظ كيف setState()يعمل. يمكنك تمرير كائن يحتوي على جزء (أجزاء) من الحالة التي تريد تحديثها. وبعبارة أخرى، فإن الكائن الذي يمر على مفاتيح المقابلة للمفاتيح في الدولة المكونة، ثم setState()بتحديث أو تضع الدولة من خلال دمج الكائن إلى الدولة. وبالتالي ، "مجموعة الدول".

ما ربما لم تكن تعرفه

تذكر كيف قلنا setState()يعمل؟ حسنًا ، ماذا لو أخبرتك أنه بدلاً من تمرير كائن ، يمكنك تمرير وظيفة ؟

نعم. setState()يقبل أيضًا وظيفة. تقبل الوظيفة الحالة السابقة والدعائم الحالية للمكون الذي تستخدمه لحساب وإرجاع الحالة التالية. انظر أدناه:

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

لاحظ أن setState()هذه وظيفة ، ونحن نمرر لها وظيفة أخرى (البرمجة الوظيفية ... الوظيفية setState ). للوهلة الأولى ، قد يبدو هذا قبيحًا ، والعديد من الخطوات لمجرد ضبط الحالة. لماذا تريد أن تفعل هذا؟

لماذا تمرر وظيفة إلى setState?

الشيء هو أن تحديثات الحالة قد تكون غير متزامنة.

فكر فيما يحدث عندما setState()يتم الاتصال. ستقوم React أولاً بدمج الكائن الذي مررته إلى setState()الحالة الحالية. ثم سيبدأ شيء المصالحة هذا . ستنشئ شجرة عنصر React جديدة (تمثيل كائن لواجهة المستخدم الخاصة بك) ، وتباين الشجرة الجديدة مقابل الشجرة القديمة ، وتكتشف ما الذي تغير بناءً على الكائن الذي مررت إليه setState()، ثم أخيرًا تحديث DOM.

يا للعجب! الكثير من العمل! في الواقع ، هذا ملخص مبسط للغاية. لكن ثق في React!

React ليست مجرد "حالة ثابتة".

نظرًا لحجم العمل المتضمن ، setState()قد لا يؤدي الاتصال إلى تحديث حالتك على الفور .

قد يُجمع React عدة setState()مكالمات في تحديث واحد للأداء.

ماذا تعني React بهذا؟

أولاً ، قد تعني "المكالمات المتعددة setState()" الاتصال setState()داخل وظيفة واحدة أكثر من مرة ، مثل هذا:

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

الآن عندما تواجه React " مكالمات متعددة setState()" ، بدلاً من القيام بـ "حالة الضبط " ثلاث مرات كاملة ، ستتجنب React هذا الكم الهائل من العمل الذي وصفته أعلاه وتقول لنفسها بذكاء: "لا! لن أتسلق هذا الجبل ثلاث مرات ، وأنا أحمل وأحدث جزءًا من الحالة في كل رحلة. لا ، أفضل الحصول على حاوية ، وتجميع كل هذه الشرائح معًا ، وإجراء هذا التحديث مرة واحدة فقط. " وهذا يا أصدقائيالتجميع !

تذكر أن ما تمرره setState()هو كائن عادي. الآن ، افترض أنه في أي وقت يواجه React " مكالمات متعددة setState()" ، فإنه يقوم بعمل التجميع عن طريق استخراج جميع الكائنات التي تم تمريرها إلى كل setState()استدعاء ، ودمجها معًا لتكوين كائن واحد ، ثم تستخدم هذا الكائن الفردي للقيام به setState().

قد تبدو الكائنات المدمجة في JavaScript كما يلي:

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

يُعرف هذا النمط بتكوين الكائن.

في JavaScript ، الطريقة التي يعمل بها "دمج" الكائنات أو تكوينها هي: إذا كانت الكائنات الثلاثة لها نفس المفاتيح ، فإن قيمة مفتاح آخر كائن تم تمريره Object.assign()للفوز. فمثلا:

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

نظرًا لكون youالكائن الأخير تم دمجه we، فإن القيمة nameالموجودة في youالكائن - "اسمك" - تتجاوز القيمة nameالموجودة في meالكائن. لذا فإن "اسمك" يدخل في weالموضوع ... youفوز! :)

وبالتالي ، إذا اتصلت setState()بكائن عدة مرات - تمرير كائن في كل مرة - ستندمج React . أو بعبارة أخرى ، سوف يؤلف كائنًا جديدًا من بين الكائنات المتعددة التي مررناها عليه. وإذا احتوى أي من الكائنات على نفس المفتاح ، فسيتم تخزين قيمة مفتاح آخر كائن له نفس المفتاح. حق؟

هذا يعني أنه ، بالنظر إلى increaseScoreBy3وظيفتنا أعلاه ، ستكون النتيجة النهائية للدالة 1 بدلاً من 3 ، لأن React لم تُحدِّث الحالة على الفور بالترتيب الذي أطلقناه setState(). لكن أولًا ، قامت React بتكوين كل الكائنات معًا ، مما أدى إلى ما يلي: {score : this.state.score + 1}، ثم فعلت "حالة المجموعة" مرة واحدة فقط - مع الكائن المكون حديثًا. شيء من هذا القبيل: User.setState({score : this.state.score + 1}.

لكي نكون واضحين للغاية ، فإن تمرير الكائن إلى setState()ليس هو المشكلة هنا. المشكلة الحقيقية هي تمرير الكائن إلى setState()عندما تريد حساب الحالة التالية من الحالة السابقة. لذا توقف عن فعل هذا. إنه ليس آمنًا!

لأن this.propsو this.stateيمكن تحديثها بشكل غير متزامن، يجب أن لا تعتمد على قيمهم لحساب الدولة القادمة.

هذا قلم من Sophia Shoemaker يوضح هذه المشكلة. العب بها ، وانتبه إلى كل من الحلول السيئة والحسنة في هذا القلم:

مجموعة وظيفية الدولة للإنقاذ

إذا لم تقضِ وقتًا في اللعب بالقلم أعلاه ، فإنني أوصيك بشدة أن تفعل ذلك ، حيث سيساعدك ذلك على فهم المفهوم الأساسي لهذه المشاركة.

بينما كنت تلعب بالقلم أعلاه ، لا شك أنك رأيت أن setState الوظيفية أصلحت مشكلتنا. لكن كيف بالضبط؟

دعنا نستشير أوبرا رياكت - دان.

لاحظ الإجابة التي قدمها. عندما تفعل setState الوظيفية ...

سيتم وضع التحديثات في قائمة الانتظار وتنفيذها لاحقًا بالترتيب الذي تم استدعاؤها.

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.

ترميز سعيد!