تتضمن مواصفات ES2023 الآن أربع دوال للمصفوفات—toSorted وtoReversed وtoSpliced وwith—والتي تعيد مصفوفات جديدة بدلاً من تعديل المصفوفة الأصلية. في React والمكتبات الأخرى لواجهات المستخدم التي تعتمد على الحالة غير القابلة للتغيير (immutable state)، تتيح هذه الدوال للمطورين استبدال حيل عامل الانتشار (spread operator) التي لطالما كانت مصدراً للأخطاء والأكواد المتكررة (boilerplate).

لماذا يهم هذا التغيير

يقرر React ما إذا كان يجب إعادة تصيير (re-render) المكون من خلال مقارنة مرجع الحالة السابقة بالمرجع الجديد. إذا ظل المرجع دون تغيير، يفترض React أنه لم يتغير شيء. تقوم دالة Array.prototype.sort التقليدية بفرز المصفوفة في مكانها (in place) وتعيد نفس المرجع، لذا فإن استدعاءً مثل setTasks(prev => prev.sort(fn)) يجعل React غير قادر على رؤية التحديث. تظل واجهة المستخدم عالقة على بيانات قديمة، وهو خطأ يظهر كثيراً في المشاريع الحقيقية.

عمل المطورون على حل هذه المشكلة عن طريق استنساخ المصفوفة أولاً—عادةً باستخدام عامل الانتشار (spread operator)—بحيث تنتج خطوة الفرز مرجعاً جديداً:

setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

هذا النمط يعمل، لكنه يضيف تعقيداً ومن السهل نسيانه. توفر دوال ES2023 الجديدة طريقة مباشرة وسهلة القراءة لإنتاج مصفوفة جديدة مع إبقاء الأصل دون تغيير.

الدوال الأربع غير المعدلة (non-mutating)

  • toSorted(compareFn?) – تعمل مثل sort ولكنها تعيد نسخة مرتبة. لا يوجد تغيير على المصفوفة المصدر.
  • toReversed() – تحل محل reverse. تعيد نسخة معكوسة، مع الحفاظ على الترتيب الأصلي.
  • toSpliced(start, deleteCount, ...items) – تحاكي splice دون آثار جانبية. المصفوفة الناتجة تعكس الإضافة أو الإزالة، بينما تظل المصفوفة المصدر كما هي.
  • with(index, value) – تستبدل العنصر الموجود في index بالقيمة value وتعيد مصفوفة جديدة. وهي تحل محل النمط الشائع لاستخدام map أو استبدال العناصر عبر عامل الانتشار.

جميع الدوال الأربع هي جزء من معيار ECMAScript وهي متوفرة في الإصدارات الحالية من المتصفحات الرئيسية و Node.js 20.

كيف يبدو الكود الآن

فرز قائمة

// Before
setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

// After
setTasks(prev => prev.toSorted((a, b) => b.priority - a.priority));

تحديث عنصر واحد

// Before
setItems(prev =>
  prev.map((item, i) => (i === idx ? newItem : item))
);

// After
setItems(prev => prev.with(idx, newItem));

عكس مصفوفة

setLogs(prev => prev.toReversed());

إزالة عنصر

setTags(prev => prev.toSpliced(removeIdx, 1));

تزيل الصيغة الجديدة الحاجة إلى عوامل انتشار إضافية أو حلقات التكرار (mapping loops)، مما يجعل تحديثات الحالة أسهل في القراءة وأقل عرضة للأخطاء.

من المستفيد ومن قد يتردد

المطورون الذين يستخدمون React أو Vue أو Redux أو Zustand أو أي إطار عمل يتوقع هياكل بيانات غير قابلة للتغيير (immutable) سيحصلون على نموذج ذهني أوضح: استدعِ الدالة، احصل على مصفوفة جديدة، ثم مررها إلى دالة التعيين (setter). كما يمكن أن يقلل تقليل الأكواد المتكررة (boilerplate) من زمن دورات التصيير ببضعة أجزاء من الثانية، لأن المحرك يتجنب إنشاء نسخة وسيطة قبل الفرز.

الفرق التي تستخدم متصفحات قديمة قد تحتاج إلى تضمين polyfills. هذه الدوال غير موجودة في الإصدارات القديمة من Safari أو Internet Explorer، لذا فإن بناء الإنتاج (production build) الذي يستهدف تلك المنصات يجب أن يتضمن حلاً بديلاً (fallback). يضيف ذلك عبئاً طفيفاً على حجم الحزمة (bundle size)، ولكن المقايضة غالباً ما تستحق مكاسب سهولة القراءة.

مؤلفو المكتبات قد يحتاجون إلى تحديث تعريفات الأنواع (مثل TypeScript) لإظهار التوقيعات (signatures) الجديدة. وإلى أن تصل هذه التعريفات إلى حزم @types الرسمية، قد يواجه المطورون أخطاء مؤقتة في الأنواع.

ما يجب مراقبته لاحقاً

  • مقاييس الاعتماد – قد تضيف أدوات مثل ESLint قريباً قواعد تكتشف استدعاءات المصفوفات المعدلة (mutable) في دوال تعيين الحالة، مما يدفع المطورين نحو الدوال الجديدة.
  • دراسات الأداء – تشير الاختبارات الأولية إلى أن الدوال الأصلية غير المعدلة (non-mutating) أسرع من استنساخ المصفوفة باستخدام عامل الانتشار متبوعاً بعملية تعديل، ولكن البيانات الواقعية ستؤكد هذا التأثير.
  • مقترحات إضافية – تستمر لجنة ECMAScript في استكشاف واجهات برمجة تطبيقات (APIs) غير قابلة للتغيير افتراضياً؛ ومراقبة المراحل القادمة قد تكشف عن المزيد من الدوال المساعدة التي تتبع نفس النمط.

الخلاصة

تمنح أحدث مواصفات ECMAScript مطوري واجهات المستخدم طريقة مدمجة وموجزة للحفاظ على عدم قابلية الحالة للتغيير (immutability) دون الحاجة إلى تعقيدات عامل الانتشار (spread-operator gymnastics) التي تسببت في عدد لا يحصى من الأخطاء. من خلال استبدال sort وreverse وsplice والاستبدالات القائمة على الفهرس (index) بـ toSorted وtoReversed وtoSpliced وwith، فإنك تسمح لآلية اكتشاف التغييرات في React بالعمل كما هو مخطط لها وتجعل كودك أسهل في القراءة. إذا كانت المتصفحات المستهدفة تدعم الدوال الجديدة — أو كنت مستعداً لاستخدام polyfills — فقد حان الوقت للتخلي عن الأنماط القديمة وترك اللغة تقوم بالعمل الشاق.