تخطَّ إلى المحتوى

الـ Records والـ Patterns في Dart 3

الدرس 13 من 14 · Dart

ماذا لو أردت أن تعيد الدالة قيمتين معاً: اسماً وعمراً؟ قديماً كنت تكتب كلاساً كاملاً لهذا الغرض، أو تستخدم قائمة فتضيع الأنواع. منذ 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).

$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.2
9.0
من 2.0 إلى 7.0

الآن نكتب range.min و range.max بوضوح. والصيغة var (:min, :max) تفكّك الحقول المسماة إلى متغيرين بالاسمين نفسيهما.

الأنماط لا تعمل مع السجلات فقط، بل تفكّك القوائم والخرائط أيضاً:

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 1
30
[30, 40]
قلم بـ 5
دفتر بـ 12
  • السطر (a, b) = (b, a); يبدّل قيمتي متغيرين دون متغير ثالث.
  • نمط القائمة يأخذ أول عنصرين، و ...rest تجمع الباقي في قائمة.
  • وفي الحلقة فكّكنا كل عنصر من الخريطة إلى مفتاحه وقيمته مباشرة.

تعرفت على تعبير 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 ...) تتحقق من الشكل وتستخرج القيم معاً، وإذا لم يطابق الشكل فلن يُنفَّذ البلوك:

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
بيانات ناقصة أو غير صحيحة
بيانات ناقصة أو غير صحيحة

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

اكتب دالة (int, int) minMax(List<int> numbers) تعيد أصغر رقم وأكبر رقم في القائمة. ثم فكّك الناتج إلى متغيرين واطبعهما. جرّبها بالقائمة [8, 3, 15, 6]:

الناتج
الأصغر: 3
الأكبر: 15
  • البدء من $0: حقول السجل تبدأ من $1، عكس فهارس القوائم.
  • محاولة تعديل حقل: user.$1 = 'عمر' مرفوضة. السجل لا يتغير، فأنشئ سجلاً جديداً.
  • نمط قائمة بعدد مختلف: var [a, b] = [1, 2, 3]; يسبب خطأ عند التشغيل، لأن العدد لا يطابق. استخدم ... لتجاهل الباقي.
  • ترتيب خاطئ في switch: الحالة العامة مثل int n إذا جاءت أولاً فستلتقط كل الأرقام، ولن تصل Dart للحالات المحددة بعدها.

سؤال

كيف نصل إلى القيمة الأولى في السجل var point = (4, 7);؟

سؤال

بعد var [x, ...others] = [5, 6, 7];، ما قيمة others؟

  • الـ Record يجمع عدة قيم بين قوسين، ويصلح لإرجاع أكثر من قيمة من دالة.
  • الحقول غير المسماة $1 و $2، والمسماة تُكتب بين { } في النوع.
  • الأنماط تفكّك السجلات والقوائم والخرائط إلى متغيرات.
  • switch مع الأنماط يفحص النوع والشكل، و when تضيف شرطاً.
  • if-case مثالية للتحقق من بيانات JSON قبل استخدامها.