تحديث المصفوفات في الحالة
المصفوفات قابلة للتغيير في JavaScript، ولكن يجب أن تعاملها على أنها غير قابلة للتغيير عند تخزينها في الحالة. تمامًا كما هو الحال مع الكائنات، عندما تريد تحديث مصفوفة مخزنة في الحالة، تحتاج إلى إنشاء مصفوفة جديدة (أو عمل نسخة من مصفوفة موجودة)، ثم تعيين الحالة لاستخدام المصفوفة الجديدة.
سوف تتعلم
- كيفية إضافة أو إزالة أو تغيير العناصر في مصفوفة في حالة React
- كيفية تحديث كائن داخل مصفوفة
- كيفية جعل نسخ المصفوفات أقل تكرارًا باستخدام Immer
تحديث المصفوفات دون تغيير
في JavaScript، المصفوفات هي مجرد نوع آخر من الكائنات.كما هو الحال مع الكائنات،يجب أن تعامل المصفوفات في حالة React على أنها للقراءة فقط.هذا يعني أنه لا يجب إعادة تعيين العناصر داخل مصفوفة مثلarr[0] = 'bird'، كما لا يجب استخدام الطرق التي تغير المصفوفة، مثلpush() و pop().
بدلاً من ذلك، في كل مرة تريد تحديث مصفوفة، سترغب في تمرير مصفوفةجديدةإلى دالة تعيين الحالة الخاصة بك. للقيام بذلك، يمكنك إنشاء مصفوفة جديدة من المصفوفة الأصلية في حالتك عن طريق استدعاء طرقها غير المُغيرة مثلfilter() و map(). ثم يمكنك تعيين حالتك إلى المصفوفة الجديدة الناتجة.
إليك جدول مرجعي للعمليات الشائعة على المصفوفات. عند التعامل مع المصفوفات داخل حالة React، ستحتاج إلى تجنب الطرق في العمود الأيسر، والتفضيل بدلاً من ذلك للطرق في العمود الأيمن:
بدلاً من ذلك، يمكنكاستخدام Immerوالذي يسمح لك باستخدام الطرق من كلا العمودين.
مأزق
للأسف،slice و spliceلهما اسمان متشابهان ولكنهما مختلفان جدًا:
sliceيتيح لك نسخ مصفوفة أو جزء منها.spliceيغيرالمصفوفة (لإدراج أو حذف العناصر).
في React، ستستخدمslice(بدونp!) كثيرًا لأنك لا تريد تغيير الكائنات أو المصفوفات في الحالة.تحديث الكائناتيشرح ما هو التغيير ولماذا لا يُنصح به للحالة.
الإضافة إلى مصفوفة
push()سيغير المصفوفة، وهو ما لا تريده:
بدلاً من ذلك، أنشئ مصفوفةجديدةتحتوي على العناصر الحاليةوعنصرًا جديدًا في النهاية. هناك عدة طرق للقيام بذلك، ولكن أسهلها هي استخدام بناء جملة...انتشار المصفوفة:
الآن يعمل بشكل صحيح:
يسمح لك بناء جملة انتشار المصفوفة أيضًا بإضافة عنصر في البداية بوضعهقبلالمصفوفة الأصلية...artists:
بهذه الطريقة، يمكن للانتشار أن يقوم بوظيفة كل منpush()بإضافة العناصر إلى نهاية المصفوفة وunshift()بإضافة العناصر إلى بداية المصفوفة. جرب ذلك في الحقل الرملي أعلاه!
إزالة عنصر من مصفوفة
أسهل طريقة لإزالة عنصر من مصفوفة هيتصفيته خارجًا. بمعنى آخر، ستنتج مصفوفة جديدة لن تحتوي على ذلك العنصر. للقيام بذلك، استخدم طريقةfilter، على سبيل المثال:
انقر على زر "Delete" عدة مرات، وانظر إلى معالج النقر الخاص به.
هنا،artists.filter(a => a.id !== artist.id)تعني "أنشئ مصفوفة تتكون من أولئكartistsالذين تختلف معرفاتهم عنartist.id". بمعنى آخر، سيصفّي زر "Delete" الخاص بكل فنانذلكالفنان خارج المصفوفة، ثم يطلب إعادة التصيير باستخدام المصفوفة الناتجة. لاحظ أنfilterلا يعدل المصفوفة الأصلية.
تحويل مصفوفة
إذا كنت تريد تغيير بعض أو كل عناصر المصفوفة، يمكنك استخدامmap()لإنشاء مصفوفةجديدة. يمكن للدالة التي ستمررها إلىmapأن تقرر ما يجب فعله مع كل عنصر، بناءً على بياناته أو فهرسه (أو كليهما).
في هذا المثال، تحتوي مصفوفة على إحداثيات دائرتين ومربع. عند الضغط على الزر، فإنه يحرك الدوائر فقط لأسفل بمقدار 50 بكسل. ويقوم بذلك عن طريق إنتاج مصفوفة جديدة من البيانات باستخدامmap():
استبدال العناصر في مصفوفة
من الشائع بشكل خاص الرغبة في استبدال عنصر واحد أو أكثر في مصفوفة. التعيينات مثلarr[0] = 'bird'تقوم بتغيير المصفوفة الأصلية، لذا بدلاً من ذلك سترغب في استخدامmapلهذا أيضًا.
لاستبدال عنصر، أنشئ مصفوفة جديدة باستخدامmap. داخل استدعاءmapالخاص بك، ستتلقى فهرس العنصر كوسيط ثانٍ. استخدمه لتقرر ما إذا كنت سترجع العنصر الأصلي (الوسيط الأول) أو شيئًا آخر:
الإدراج في مصفوفة
في بعض الأحيان، قد ترغب في إدراج عنصر في موضع معين ليس في البداية ولا في النهاية. للقيام بذلك، يمكنك استخدام...صيغة انتشار المصفوفة مع طريقةslice(). تتيح لك طريقةslice()"قطع شريحة" من المصفوفة. لإدراج عنصر، ستقوم بإنشاء مصفوفة تنشر الشريحةقبلنقطة الإدراج، ثم العنصر الجديد، ثم بقية المصفوفة الأصلية.
في هذا المثال، يقوم زر الإدراج دائمًا بالإدراج عند الفهرس1:
إجراء تغييرات أخرى على مصفوفة
هناك بعض الأشياء التي لا يمكنك القيام بها باستخدام صيغة الانتشار والطرق غير المتحولة مثلmap() و filter()وحدها. على سبيل المثال، قد ترغب في عكس أو ترتيب مصفوفة. طرق JavaScriptreverse() و sort()تقوم بتحويل المصفوفة الأصلية، لذا لا يمكنك استخدامها مباشرة.
ومع ذلك، يمكنك نسخ المصفوفة أولاً، ثم إجراء التغييرات عليها.
على سبيل المثال:
هنا، تستخدم صيغة الانتشار[...list]لإنشاء نسخة من المصفوفة الأصلية أولاً. الآن بعد أن أصبح لديك نسخة، يمكنك استخدام الطرق المتحولة مثلnextList.reverse()أوnextList.sort()، أو حتى تعيين عناصر فردية باستخدامnextList[0] = "something".
ومع ذلك،حتى إذا قمت بنسخ مصفوفة، لا يمكنك تحويل العناصر الموجودةداخلهامباشرة.وذلك لأن النسخ يكون سطحيًا - ستحتوي المصفوفة الجديدة على نفس العناصر الموجودة في المصفوفة الأصلية. لذا إذا قمت بتعديل كائن داخل المصفوفة المنسوخة، فأنت تقوم بتحويل الحالة الموجودة. على سبيل المثال، كود مثل هذا يمثل مشكلة.
على الرغم من أنnextList و listهما مصفوفتان مختلفتان، إلا أنnextList[0] و list[0]يشيران إلى نفس الكائن.لذا بتغييرnextList[0].seen، فأنت تقوم أيضًا بتغييرlist[0].seen. هذا تحويل للحالة، والذي يجب عليك تجنبه! يمكنك حل هذه المشكلة بطريقة مشابهة لـتحديث الكائنات المتداخلة في JavaScript—عن طريق نسخ العناصر الفردية التي تريد تغييرها بدلاً من تحويلها. إليك الطريقة.
تحديث الكائنات داخل المصفوفات
الكائنات ليستفي الواقعموجودة "داخل" المصفوفات. قد تبدو "داخل" في الكود، لكن كل كائن في مصفوفة هو قيمة منفصلة، تشير إليها المصفوفة. لهذا السبب يجب أن تكون حذرًا عند تغيير الحقول المتداخلة مثلlist[0]. قد تشير قائمة الأعمال الفنية لشخص آخر إلى نفس العنصر في المصفوفة!
عند تحديث الحالة المتداخلة، تحتاج إلى إنشاء نسخ من النقطة التي تريد التحديث منها، وصولاً إلى المستوى الأعلى.دعنا نرى كيف يعمل هذا.
في هذا المثال، تحتوي قائمتان منفصلتان للأعمال الفنية على نفس الحالة الأولية. من المفترض أن تكونا معزولتين، ولكن بسبب تحويل، تمت مشاركة حالتهما عن طريق الخطأ، وتحديد مربع اختيار في قائمة واحدة يؤثر على القائمة الأخرى:
تكمن المشكلة في كود مثل هذا:
على الرغم من أن المصفوفةmyNextListنفسها جديدة، فإنالعناصر نفسهاهي نفسها الموجودة في المصفوفة الأصليةmyList. لذا فإن تغييرartwork.seenيغير عنصر العمل الفنيالأصلي. هذا العنصر موجود أيضًا فيyourList، مما يتسبب في الخطأ. قد يكون التفكير في أخطاء كهذه صعبًا، لكن لحسن الحظ تختفي إذا تجنبت تغيير الحالة.
يمكنك استخدامmapلاستبدال عنصر قديم بإصداره المحدث دون تغيير.
هنا،...هو بناء جملة نشر الكائن المستخدم لـإنشاء نسخة من كائن.
بهذا النهج، لا يتم تغيير أي من عناصر الحالة الموجودة، ويتم إصلاح الخطأ:
بشكل عام،يجب عليك فقط تغيير الكائنات التي أنشأتها للتو.إذا كنت تقوم بإدراج عمل فنيجديد، يمكنك تغييره، ولكن إذا كنت تتعامل مع شيء موجود بالفعل في الحالة، فأنت بحاجة إلى عمل نسخة.
كتابة منطق تحديث موجز باستخدام Immer
يمكن أن يصبح تحديث المصفوفات المتداخلة دون تغيير متكررًا بعض الشيء.تمامًا كما هو الحال مع الكائنات:
- عمومًا، لا يجب أن تحتاج إلى تحديث الحالة لأكثر من بضع مستويات عمقًا. إذا كانت كائنات حالتك عميقة جدًا، فقد ترغب فيإعادة هيكلتها بشكل مختلفبحيث تكون مسطحة.
- إذا كنت لا ترغب في تغيير بنية حالتك، فقد تفضل استخدامImmer، والذي يتيح لك الكتابة باستخدام بناء الجملة المريح ولكن المتغير ويتولى إنتاج النسخ نيابة عنك.
إليك مثال قائمة الأعمال الفنية المكتوب باستخدام Immer:
لاحظ كيف أنه مع Immer، أصبحالتغيير المباشر مثلartwork.seen = nextSeenمقبولاً الآن:
هذا لأنك لا تغير الحالةالأصلية، بل تغير كائنdraftالخاص الذي توفره Immer. وبالمثل، يمكنك تطبيق طرق تغيير مباشر مثلpush() و pop()على محتوى الـdraft.
خلف الكواليس، تقوم Immer دائمًا ببناء الحالة التالية من الصفر وفقًا للتغييرات التي أجريتها على الـdraft. هذا يحافظ على موجزية معالجات الأحداث الخاصة بك دون تغيير الحالة مباشرةً أبدًا.
ملخص
- يمكنك وضع المصفوفات في الحالة، لكن لا يمكنك تغييرها.
- بدلاً من تغيير مصفوفة مباشرةً، أنشئنسخة جديدةمنها، وقم بتحديث الحالة إليها.
- يمكنك استخدام صيغة انتشار المصفوفة
[...arr, newItem]لإنشاء مصفوفات تحتوي على عناصر جديدة. - يمكنك استخدام
filter()وmap()لإنشاء مصفوفات جديدة تحتوي على عناصر مُرشحة أو مُحوّلة. - يمكنك استخدام Immer للحفاظ على شفرة موجزة.
Try out some challenges
Challenge 1 of 4:Update an item in the shopping cart #
Fill in the handleIncreaseClick logic so that pressing ”+” increases the corresponding number:
