الـ Records والـ Patterns في Dart 3
ماذا لو أردت أن تعيد الدالة قيمتين معاً: اسماً وعمراً؟ قديماً كنت تكتب كلاساً كاملاً لهذا الغرض، أو تستخدم قائمة فتضيع الأنواع. منذ Dart 3 صار الحل سطراً واحداً بفضل Records (السجلات)، ومعها Patterns (الأنماط) التي تفكّك البيانات بأناقة. هاتان الميزتان من أحدث ما في اللغة، وستجعلان كودك أقصر وأوضح.
Record: عدة قيم في حزمة واحدة
Section titled “Record: عدة قيم في حزمة واحدة”الـ Record مجموعة قيم بين قوسين عاديين، ولكل قيمة نوعها. تخيّله مثل تذكرة قطار: ورقة صغيرة تحمل عدة معلومات معاً (الاسم ورقم المقعد)، ولا تحتاج ملفاً رسمياً لكل راكب.
(String, int) getUser() { return ('سارة', 15);}
void main() { var user = getUser(); print(user.$1); print(user.$2);
var (name, age) = getUser(); print('$name عمرها $age');}سارة15سارة عمرها 15- النوع
(String, int)يعني: سجل فيه نص ثم رقم. - نصل للقيم بالترتيب:
$1للأولى و$2للثانية (العدّ هنا يبدأ من 1). - السطر
var (name, age) = getUser();هو الأجمل: يفكّك السجل ويضع كل قيمة في متغير. هذا أول نمط تراه، ويسمى التفكيك (Destructuring).
الحقول المسماة
Section titled “الحقول المسماة”$1 و $2 ليسا اسمين واضحين. يمكنك تسمية الحقول بوضعها بين قوسين معقوفين داخل النوع:
({double min, double max}) findRange(List<double> values) { var sorted = [...values]; sorted.sort(); return (min: sorted.first, max: sorted.last);}
void main() { var range = findRange([3.5, 9, 1.2]); print(range.min); print(range.max);
var (:min, :max) = findRange([7, 2]); print('من $min إلى $max');}1.29.0من 2.0 إلى 7.0الآن نكتب range.min و range.max بوضوح. والصيغة var (:min, :max) تفكّك الحقول المسماة إلى متغيرين بالاسمين نفسيهما.
التفكيك في كل مكان
Section titled “التفكيك في كل مكان”الأنماط لا تعمل مع السجلات فقط، بل تفكّك القوائم والخرائط أيضاً:
void main() { var a = 1, b = 2; (a, b) = (b, a); print('$a $b');
var [first, second, ...rest] = [10, 20, 30, 40]; print(first + second); print(rest);
var prices = {'قلم': 5, 'دفتر': 12}; for (var MapEntry(:key, :value) in prices.entries) { print('$key بـ $value'); }}2 130[30, 40]قلم بـ 5دفتر بـ 12- السطر
(a, b) = (b, a);يبدّل قيمتي متغيرين دون متغير ثالث. - نمط القائمة يأخذ أول عنصرين، و
...restتجمع الباقي في قائمة. - وفي الحلقة فكّكنا كل عنصر من الخريطة إلى مفتاحه وقيمته مباشرة.
switch مع الأنماط
Section titled “switch مع الأنماط”تعرفت على تعبير switch في درس الشروط. مع الأنماط يصبح أقوى بكثير: يفحص النوع والشكل ويستخرج القيم في خطوة واحدة:
String describe(Object value) { return switch (value) { int n when n < 0 => 'رقم سالب', int n => 'رقم صحيح: $n', String s => 'نص طوله ${s.length}', [var first, ...] => 'قائمة تبدأ بـ $first', (int x, int y) => 'نقطة ($x, $y)', _ => 'شيء آخر', };}
void main() { print(describe(-4)); print(describe(12)); print(describe('Dart')); print(describe([7, 8, 9])); print(describe((2, 3))); print(describe(1.5));}رقم سالبرقم صحيح: 12نص طوله 4قائمة تبدأ بـ 7نقطة (2, 3)شيء آخرint nتطابق أي رقم صحيح وتضعه فيn.- الكلمة
whenتضيف شرطاً إضافياً يسمى الحارس (Guard). - الحالات تُفحص بالترتيب، ولذلك وضعنا «السالب» قبل «أي رقم صحيح».
- الشرطة السفلية
_تطابق أي شيء، وهي الحالة الافتراضية.
if-case: التحقق من شكل JSON
Section titled “if-case: التحقق من شكل JSON”عندما تصلك بيانات من الإنترنت، لا تضمن أن فيها كل ما تتوقعه. الصيغة if (... case ...) تتحقق من الشكل وتستخرج القيم معاً، وإذا لم يطابق الشكل فلن يُنفَّذ البلوك:
void show(Map<String, Object> json) { if (json case {'name': String name, 'age': int age}) { print('$name عمره $age'); } else { print('بيانات ناقصة أو غير صحيحة'); }}
void main() { show({'name': 'عمر', 'age': 16}); show({'name': 'ليلى'}); show({'name': 'خالد', 'age': 'عشرون'});}عمر عمره 16بيانات ناقصة أو غير صحيحةبيانات ناقصة أو غير صحيحةسطر واحد تأكد أن المفتاحين موجودان، وأن الاسم نص، وأن العمر رقم. بدون الأنماط كنت ستكتب أربعة شروط متداخلة.
جرّب بنفسك
Section titled “جرّب بنفسك”اكتب دالة (int, int) minMax(List<int> numbers) تعيد أصغر رقم وأكبر رقم في القائمة. ثم فكّك الناتج إلى متغيرين واطبعهما. جرّبها بالقائمة [8, 3, 15, 6]:
الأصغر: 3الأكبر: 15أخطاء شائعة
Section titled “أخطاء شائعة”- البدء من
$0: حقول السجل تبدأ من$1، عكس فهارس القوائم. - محاولة تعديل حقل:
user.$1 = 'عمر'مرفوضة. السجل لا يتغير، فأنشئ سجلاً جديداً. - نمط قائمة بعدد مختلف:
var [a, b] = [1, 2, 3];يسبب خطأ عند التشغيل، لأن العدد لا يطابق. استخدم...لتجاهل الباقي. - ترتيب خاطئ في switch: الحالة العامة مثل
int nإذا جاءت أولاً فستلتقط كل الأرقام، ولن تصل Dart للحالات المحددة بعدها.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
كيف نصل إلى القيمة الأولى في السجل var point = (4, 7);؟
الحقول غير المسماة في السجل نصل إليها بـ $1 و $2، والعدّ يبدأ من 1.
سؤال
بعد var [x, ...others] = [5, 6, 7];، ما قيمة others؟
x تأخذ العنصر الأول 5، و ...others تجمع ما بقي في قائمة: 6 و 7.
الخلاصة
Section titled “الخلاصة”- الـ Record يجمع عدة قيم بين قوسين، ويصلح لإرجاع أكثر من قيمة من دالة.
- الحقول غير المسماة
$1و$2، والمسماة تُكتب بين{ }في النوع. - الأنماط تفكّك السجلات والقوائم والخرائط إلى متغيرات.
switchمع الأنماط يفحص النوع والشكل، وwhenتضيف شرطاً.if-caseمثالية للتحقق من بيانات JSON قبل استخدامها.