v19.2Latest

تحديث الكائنات في الحالة

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

سوف تتعلم
  • كيفية تحديث كائن في حالة React بشكل صحيح
  • كيفية تحديث كائن متداخل دون تغييره مباشرة
  • ما هي عدم القابلية للتغيير، وكيف لا تكسرها
  • كيف تجعل نسخ الكائنات أقل تكرارًا باستخدام Immer

ما هو التغيير المباشر؟

يمكنك تخزين أي نوع من قيم JavaScript في الحالة.

حتى الآن كنت تعمل مع الأرقام والسلاسل النصية والقيم المنطقية. هذه الأنواع من قيم JavaScript هي "غير قابلة للتغيير"، أي لا يمكن تغييرها أو هي "للقراءة فقط". يمكنك تشغيل إعادة التصيير لـاستبدالقيمة:

تغيرت حالةx من 0إلى5، لكنالرقم0نفسهلم يتغير. ليس من الممكن إجراء أي تغييرات على القيم الأولية المدمجة مثل الأرقام والسلاسل النصية والقيم المنطقية في JavaScript.

الآن فكر في كائن موجود في الحالة:

من الناحية الفنية، من الممكن تغيير محتوياتالكائن نفسه.هذا يسمى تغييرًا مباشرًا:

ومع ذلك، على الرغم من أن الكائنات في حالة React قابلة للتغيير من الناحية الفنية، يجب أن تعاملهاكما لو كانتغير قابلة للتغيير—مثل الأرقام والقيم المنطقية والسلاسل النصية. بدلاً من تغييرها مباشرة، يجب عليك دائمًا استبدالها.

عامل الحالة على أنها للقراءة فقط

بمعنى آخر، يجب أنتعامل أي كائن JavaScript تضعه في الحالة على أنه للقراءة فقط.

يحتوي هذا المثال على كائن في الحالة لتمثيل موضع المؤشر الحالي. من المفترض أن تتحرك النقطة الحمراء عند لمس المؤشر أو تحريكه فوق منطقة المعاينة. لكن النقطة تبقى في الموضع الأولي:

المشكلة تكمن في هذا الجزء من الكود.

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

لـ تشغيل إعادة التصييرفي هذه الحالة،أنشئكائنًاجديدًا ومرره إلى دالة تعيين الحالة:

باستخدامsetPosition، أنت تخبر React:

  • استبدلpositionبهذا الكائن الجديد
  • وصِف هذا المكون مرة أخرى

لاحظ كيف تتبع النقطة الحمراء مؤشرك الآن عند لمسه أو تحويمه فوق منطقة المعاينة:

Deep Dive
التحوير المحلي مقبول

نسخ الكائنات باستخدام صيغة الانتشار

في المثال السابق، يتم إنشاء كائنpositionجديد دائمًا من موضع المؤشر الحالي. لكن في كثير من الأحيان، سترغب في تضمينبيانات موجودةكجزء من الكائن الجديد الذي تنشئه. على سبيل المثال، قد ترغب في تحديثحقل واحد فقطفي نموذج، مع الاحتفاظ بالقيم السابقة لجميع الحقول الأخرى.

حقول الإدخال هذه لا تعمل لأن معالجاتonChangeتحور الحالة:

على سبيل المثال، هذا السطر يحور الحالة من عملية تصيير سابقة:

الطريقة الموثوقة للحصول على السلوك الذي تبحث عنه هي إنشاء كائن جديد وتمريره إلىsetPerson. لكن هنا، تريد أيضًانسخ البيانات الموجودة إليهلأن حقلًا واحدًا فقط قد تغير:

يمكنك استخدام صيغة الانتشار...للكائناتحتى لا تحتاج إلى نسخ كل خاصية على حدة.

الآن النموذج يعمل!

لاحظ أنك لم تعلن عن متغير حالة منفصل لكل حقل إدخال. بالنسبة للنماذج الكبيرة، الاحتفاظ بجميع البيانات مجمعة في كائن واحد مريح جدًا — طالما تقوم بتحديثه بشكل صحيح!

لاحظ أن صيغة الانتشار...هي "سطحيّة" — فهي تنسخ الأشياء على مستوى واحد فقط. هذا يجعلها سريعة، لكنه يعني أيضًا أنه إذا أردت تحديث خاصية متداخلة، فسيتعين عليك استخدامها أكثر من مرة.

تحديث كائن متداخل

فكر في بنية كائن متداخل مثل هذه:

إذا أردت تحديثperson.artwork.city، فمن الواضح كيفية القيام بذلك باستخدام التحوير:

ولكن في React، تعامل الحالة على أنها غير قابلة للتغيير! لتغييرcity، ستحتاج أولاً إلى إنشاء كائنartworkالجديد (مملوء مسبقًا ببيانات من السابق)، ثم إنشاء كائنpersonالجديد الذي يشير إلى كائنartworkالجديد:

أو، مكتوبة كاستدعاء دالة واحد:

يصبح هذا طويلًا بعض الشيء، لكنه يعمل بشكل جيد في العديد من الحالات:

Deep Dive
الكائنات ليست متداخلة حقًا

كتابة منطق تحديث موجز باستخدام Immer

إذا كانت حالتك متداخلة بعمق، فقد ترغب في التفكير فيتسطيحها.ولكن، إذا كنت لا تريد تغيير بنية حالتك، فقد تفضل اختصارًا لنشر الكائنات المتداخلة.Immerهي مكتبة شائعة تتيح لك الكتابة باستخدام بناء الجملة المريح لكن المتحور وتتولى إنشاء النسخ نيابة عنك. باستخدام Immer، يبدو الكود الذي تكتبه كما لو كنت "تخرق القواعد" وتحور كائنًا:

لكن على عكس التحوير العادي، فإنه لا يستبدل الحالة السابقة!

Deep Dive
كيف يعمل Immer؟

لتجربة Immer:

  1. شغّلnpm install use-immerلإضافة Immer كاعتمادية
  2. ثم استبدلimport { useState } from 'react'بـimport { useImmer } from 'use-immer'

إليك المثال السابق محولًا لاستخدام Immer:

لاحظ كيف أصبحت معالجات الأحداث أكثر إيجازًا. يمكنك مزج ومطابقةuseState و useImmerفي مكون واحد كما تشاء. يُعد Immer طريقة رائعة للحفاظ على معالجات التحديث موجزة، خاصةً إذا كان هناك تداخل في حالتك، ويؤدي نسخ الكائنات إلى كود متكرر.

Deep Dive
لماذا لا يُنصح بتحوير الحالة في React؟

ملخص

  • عامل جميع الحالة في React على أنها غير قابلة للتغيير.
  • عندما تخزن كائنات في الحالة، فإن تعديلها لن يؤدي إلى تشغيل عمليات التصيير وسيغير الحالة في "لقطات" التصيير السابقة.
  • بدلاً من تعديل كائن، أنشئنسخة جديدةمنه، وقم بتشغيل إعادة التصيير عن طريق تعيين الحالة إليها.
  • يمكنك استخدام{...obj, something: 'newValue'}صيغة نشر الكائن لإنشاء نسخ من الكائنات.
  • صيغة النشر سطحية: فهي تنسخ مستوى واحد فقط في العمق.
  • لتحديث كائن متداخل، تحتاج إلى إنشاء نسخ على طول الطريق من المكان الذي تقوم بالتحديث فيه.
  • لتقليل كود النسخ المتكرر، استخدم Immer.

Try out some challenges

Challenge 1 of 3:Fix incorrect state updates #

This form has a few bugs. Click the button that increases the score a few times. Notice that it does not increase. Then edit the first name, and notice that the score has suddenly “caught up” with your changes. Finally, edit the last name, and notice that the score has disappeared completely.

Your task is to fix all of these bugs. As you fix them, explain why each of them happens.