Dart: متغيرات Dart وأنواع البيانات — خمسة طرق للإعلان
آخر تحديث: 2026-08-26
المتغيرات حاويات للبيانات، والأنواع هي شكل الحاوية — اختر الحاوية المناسبة، وسيكون كودك آمنًا.
1. ما ستتعلمه
- مقارنة طرق الإعلان الخمس: var / final / const / late / dynamic
- الأنواع المدمجة: int / double / String / bool / num
- أفضل ممارسات استنتاج النوع والتعليق الصريح
- استيفاء السلاسل والسلاسل متعددة الأسطر (علامات اقتباس ثلاثية + بادئة r)
2. قصة مطور حقيقي
(1) نقطة الألم: لماذا تعطل البرنامج بسبب خطأ إملائي في العمر؟
طُلب من سارة (متدربة جديدة) كتابة برنامج آلة حاسبة بسيط. كتبت int age = '25' (سلسلة في متغير عدد صحيح)، فحص مترجم Python: يمر. فحص مترجم JavaScript: يمر. فحص مترجم Dart 3: ❌ "نوع الوسيطة 'String' لا يمكن تعيينها للنوع 'int'". شعرت بالإحباط: "ما الفرق؟" أجابها زميلها: "Dart يحميك من الأخطاء، لكن JavaScript يلقيها عليك في وقت التشغيل."
(2) حل Dart
DART
// كود سيء — Python/JS يمر، Dart 3 يفشل
// int age = '25'; // ❌ error: نوع الوسيطة 'String' لا يمكن تعيينها للنوع 'int'
// ✅ Dart: التعليق الصريح أو استنتاج النوع
int age = 25; // نوع صريح
var inferredAge = 25; // استنتاج النوع (يعرف int من 25)
final constantAge = 25; // ثابت وقت التشغيل
const compileTimeAge = 25; // ثابت وقت الترجمة
(3) النتيجة: أخطاء وقت التشغيل → منع وقت الترجمة
| الخطأ | Python/JS | Dart 3 |
|---|---|---|
| String → int | يمر، ينفجر في وقت التشغيل | ❌ أخطاء وقت الترجمة |
| Null → int | يمر، NullPointerException | ❌ Null check error |
| int + String | يمر، concatenation | ❌ type mismatch |
| الوصول خارج الحدود | يمر، IndexError | ❌ RangeError |
TEXT
📖 للعرض فقط
قبل (Python/JS): اختبار كامل 100%، خطأ وقت التشغيل في الإنتاج
بعد (Dart): أخطاء وقت الترجمة 50+، اختبار كامل 100%، إنتاج خالٍ من الأخطاء
ROI: عيوب وقت التشغيل ↓80%
3. خمسة طرق للإعلان عن المتغيرات
(1) var — استنتاج النوع
DART
// نوع المتغير يُستنتج من القيمة
var name = 'Bob'; // String
var age = 25; // int
var height = 1.75; // double
var isActive = true; // bool
var items = [1, 2, 3]; // List<int>
var person = {'name': 'Bob'}; // Map<String, String>
// بعد الإعلان، لا يمكن تغيير النوع
var score = 100; // int
// score = 'high'; // ❌ error: نوع الوسيطة 'String' لا يمكن تعيينها للنوع 'int'
(2) final — ثابت وقت التشغيل
DART
// يمكن التعيين مرة واحدة فقط
final name = 'Bob';
// name = 'Alice'; // ❌ خطأ في وقت الترجمة: لا يمكن تعيين المتغير النهائي
// لكن يمكن أن يكون الكائن الداخلي قابلاً للتغيير
final list = [1, 2, 3];
list.add(4); // ✅ مسموح — يعدل القائمة، لا المرجع
final date = DateTime.now(); // ✅ استدعاء دالة في وقت الإعلان
// const date2 = DateTime.now(); // ❌ لا يمكن استخدام استدعاء دالة في const
(3) const — ثابت وقت الترجمة
DART
// يجب أن تكون القيمة ثابتة في وقت الترجمة
const pi = 3.14159;
const name = 'Dart';
const maxSize = 100;
// لا يمكن استخدام استدعاءات دوال أو كائنات قابلة للتغيير
// const date = DateTime.now(); // ❌
// const list = [1, 2, DateTime.now()]; // ❌
// const هو ثابت compile-time، أسرع من final
(4) late — التهيئة الكسولة
DART
// متغير لن يتم تهيئته حتى الاستخدام الأول
late String expensiveData;
// في هذه المرحلة، expensiveData لا يزال غير مهيأ
print('Step 1');
// الآن يتم التهيئة
expensiveData = _loadExpensiveData();
print('Step 2: $expensiveData');
// late final — يمكن التعيين مرة واحدة، بعد التهيئة الأولى
late final String config;
if (useProduction) {
config = 'prod-config';
} else {
config = 'dev-config';
}
print(config);
(5) dynamic — تجاوز فحص النوع
DART
// dynamic يتخطى فحص النوع الثابت
dynamic value = 'hello';
value = 42; // ✅ لا خطأ
value = [1, 2, 3]; // ✅ لا خطأ
value.foo(); // ✅ لا خطأ في وقت الترجمة، ❌ في وقت التشغيل إذا لم يكن موجودًا
// مفيد لـ JSON parsing، dynamic config، إلخ.
Map<String, dynamic> config = {
'name': 'Bob',
'age': 25,
'isAdmin': true,
};
▶ مثال: مقارنة 5 طرق للإعلان
DART
// 1. var — استنتاج النوع، قابل للتعيين
var score = 100; // int، قابل للتعيين
score = 200; // ✅
// score = 'high'; // ❌ خطأ في النوع
// 2. final — ثابت وقت التشغيل، قابل للتعيين مرة واحدة
final name = 'Bob'; // String، ثابت
// name = 'Alice'; // ❌ ثابت
// 3. const — ثابت وقت الترجمة، قابل للتعيين مرة واحدة
const pi = 3.14; // double، ثابت وقت الترجمة
// pi = 3.15; // ❌ ثابت
// 4. late — تهيئة كسولة
late String data; // لم يتم التهيئة
data = 'initialized'; // ✅
print(data); // "initialized"
// 5. dynamic — تجاوز فحص النوع
dynamic anything = 'hello';
anything = 42; // ✅
print(anything); // 42
void main() {
print('score=$score, name=$name, pi=$pi, data=$data, anything=$anything');
}
BASH
$ dart run
score=200, name=Bob, pi=3.14, data=initialized, anything=42
TEXT
📖 للعرض فقط
> **الإخراج:** var و final و const و late و dynamic هي 5 طرق للإعلان. كل طريقة لها سيناريوهات استخدام مختلفة.
(6) جدول المقارنة
| الطريقة | قابل للتعيين | وقت التقييم | فحص النوع | حالة الاستخدام |
|---|---|---|---|---|
var |
✅ | وقت التشغيل | ✅ استنتاج | المتغيرات المحلية |
final |
❌ | وقت التشغيل | ✅ استنتاج | متغيرات الفئة، المتغيرات المحلية غير القابلة للتغيير |
const |
❌ | وقت الترجمة | ✅ استنتاج | الثوابت، تكوين المشروع |
late |
✅ (تأخير) | وقت التشغيل الأول | ✅ استنتاج | التهيئة الكسولة، خصائص الفئة التي تتطلب وقتًا |
dynamic |
✅ | وقت التشغيل | ❌ تجاوز | JSON، تكوين ديناميكي، استدعاءات API |
4. الأنواع المدمجة
(1) نوع num — int و double
DART
// int — عدد صحيح (64 بت على النظام الأساسي 64 بت)
int age = 25;
int hex = 0xFF; // 255 بالنظام السداسي العشري
int binary = 0b1010; // 10 بالنظام الثنائي
int big = 1000000000000; // تريليون (int 64 بت)
// double — رقم بفاصلة عائمة (64 بت)
double price = 19.99;
double scientific = 1.5e10; // 15 مليار
double negative = -3.14;
// num — النوع الأب لـ int و double
num x = 10; // int
num y = 3.14; // double
// التحويل بين int و double
int i = 10;
double d = i.toDouble(); // 10.0
double d2 = 20.5;
int i2 = d2.toInt(); // 20 (يتجاهل الجزء العشري)
int i3 = d2.round(); // 21 (يقرّب)
// التحقق من النوع
print(i.isEven); // true
print(d.isFinite); // true
print(d.isNaN); // false
(2) نوع String
DART
// علامات الاقتباس المفردة أو المزدوجة
String name = 'Bob';
String city = "New York";
// علامات الاقتباس الثلاثية — سلاسل متعددة الأسطر (تحافظ على المسافة البادئة)
String html = '''
<div>
<h1>Hello</h1>
<p>World</p>
</div>
''';
// سلسلة خام (r prefix) — تتجاهل أحرف الهروب
String regex = r'\d+\.\d+'; // بدلاً من '\\d+\\.\\d+'
String windowsPath = r'C:\Users\Bob\file.txt';
// الاستيفاء
String name = 'Bob';
String greeting = 'Hello, $name!'; // Hello, Bob!
String calc = '1 + 2 = ${1 + 2}'; // 1 + 2 = 3
String nested = '${'Hello, $name!'.toUpperCase()}'; // HELLO, BOB!
// السلاسل متعددة الأسطر
String multi = """
Line 1
Line 2
Line 3
""";
// مقارنة مع r + multi-line
String rawMulti = r'''
No \n escape
Multi-line
''';
(3) نوع bool
DART
bool isActive = true;
bool isCompleted = false;
// العوامل المنطقية
bool result1 = true && false; // false (AND)
bool result2 = true || false; // true (OR)
bool result3 = !true; // false (NOT)
// شروط Dart الحقيقية: فقط true هي true (وليس truthy/falsy مثل JS)
bool a = true && true; // true
bool b = true && 0; // ❌ error: نوع الوسيطة 'int' لا يمكن تعيينها للنوع 'bool'
bool c = true && null; // ❌ error: نوع الوسيطة 'Null' لا يمكن تعيينها للنوع 'bool'
// مقارنة آمنة null
bool? maybe = null;
if (maybe ?? false) { // ?? توفر قيمة افتراضية
print('is true');
}
(4) نوع List
DART
// نوع صريح
List<int> numbers = [1, 2, 3, 4, 5];
List<String> names = ['Alice', 'Bob'];
// استنتاج النوع
var mixed = [1, 'two', 3]; // List<Object>
// القيم الفارغة
List<int>? maybeEmpty = null;
// خصائص شائعة
numbers.length; // 5
numbers.isEmpty; // false
numbers.first; // 1
numbers.last; // 5
numbers.contains(3); // true
numbers.indexOf(3); // 2
numbers.add(6); // [1, 2, 3, 4, 5, 6]
numbers.removeAt(0); // [2, 3, 4, 5, 6]
numbers.reversed; // (6, 5, 4, 3, 2)
// التحول
List<int> doubled = numbers.map((n) => n * 2).toList();
List<int> evens = numbers.where((n) => n.isEven).toList();
(5) نوع Map
DART
// نوع صريح
Map<String, int> ages = {
'Alice': 30,
'Bob': 25,
};
// القيم الفارغة
Map<String, int>? maybeNull = null;
// عمليات شائعة
ages['Alice']; // 30
ages['Charlie']; // null
ages['Charlie'] = 35; // إضافة
ages.length; // 3
ages.containsKey('Bob'); // true
ages.remove('Bob'); // حذف
// التكرار
ages.forEach((name, age) {
print('$name is $age');
});
(6) نوع Set
DART
// مجموعة غير مرتبة، لا تحتوي على عناصر مكررة
Set<int> numbers = {1, 2, 3, 4, 5};
Set<String> fruits = {'apple', 'banana', 'apple'}; // {'apple', 'banana'}
numbers.add(6); // {1, 2, 3, 4, 5, 6}
numbers.add(1); // {1, 2, 3, 4, 5, 6} (لا إضافة مكررة)
numbers.contains(3); // true
numbers.difference({3, 4}); // {1, 2, 5, 6}
numbers.union({7, 8}); // {1, 2, 3, 4, 5, 6, 7, 8}
numbers.intersection({3, 4, 5}); // {3, 4, 5}
▶ مثال: نظام شامل من 5 طرق إعلان + 6 أنواع
DART
// متغيرات المشروع
var projectName = 'SalesPredict'; // String (استنتاج)
final version = '1.0.0'; // String (ثابت)
const apiUrl = 'https://api.example.com'; // String (ثابت وقت الترجمة)
late final databaseConfig; // تهيئة كسولة
databaseConfig = 'postgres://localhost'; // مهمة
// التكوين
Map<String, dynamic> config = {
'name': projectName,
'version': version,
'apiUrl': apiUrl,
'databaseConfig': databaseConfig,
};
// قائمة الميزات
List<String> features = [
'sales-forecasting',
'user-segmentation',
'anomaly-detection',
];
// علامات الميزات
Set<String> enabledFeatures = {'sales-forecasting', 'user-segmentation'};
// عدّاد
int recordCount = 0;
// درجة الحرارة
double temperature = 23.5;
// حالة التفعيل
bool isHealthy = true;
void main() {
print('المشروع: $projectName v$version');
print('API: $apiUrl');
print('الميزات: $features');
print('الميزات المفعّلة: $enabledFeatures');
print('العدادات: $recordCount');
print('درجة الحرارة: $temperature°C');
print('الحالة الصحية: $isHealthy');
print('التكوين: $config');
}
BASH
$ dart run
المشروع: SalesPredict v1.0.0
API: https://api.example.com
الميزات: [sales-forecasting, user-segmentation, anomaly-detection]
الميزات المفعّلة: {sales-forecasting, user-segmentation}
العدادات: 0
درجة الحرارة: 23.5°C
الحالة الصحية: true
التكوين: {name: SalesPredict, version: 1.0.0, apiUrl: https://api.example.com, databaseConfig: postgres://localhost}
TEXT
📖 للعرض فقط
> **الإخراج:** 5 طرق إعلان × 6 أنواع = 30 طريقة لاستخدام المتغيرات. اختر الطريقة المناسبة حسب السيناريو.
5. استنتاج النوع مقابل التعليق الصريح
(1) استنتاج النوع — معظم الوقت
DART
// ✅ يفضل استنتاج النوع
var name = 'Bob';
var age = 25;
var isActive = true;
var items = <int>[1, 2, 3];
// لماذا؟
// 1. أقل ضوضاءً بصريًا
// 2. Dart سيختار النوع الأكثر دقة
// 3. تحديث النوع تلقائيًا عند تعديل القيمة
(2) التعليق الصريح — API عام
DART
// ✅ يفضل التعليق الصريح
class User {
final String name; // واضح للقراء
final int age; // IDE يعرض النوع في تلميحات الأدوات
final List<String> tags;
}
// 1. واجهة عامة (API) — القراء لا يعرفون نوع الاستنتاج
// 2. خصائص الفئة — IDE يعرضها في الإكمال التلقائي
// 3. معلمات الدالة — لتوضيح القصد
String greet(String name) => 'Hello, $name';
(3) متى تستخدم أيهما — جدول
| السيناريو | var | تعليق صريح |
|---|---|---|
| متغير محلي قصير | ✅ | - |
| خاصية الفئة | - | ✅ |
| معامل الدالة | - | ✅ |
| قيمة إرجاع الدالة | - | ✅ |
| المتغير العام / static | - | ✅ |
| التكوين | - | ✅ |
(4) مثال: نفس الكود بطريقتين
DART
// ✅ الأسلوب 1: استنتاج النوع (مفضل للمتغيرات المحلية)
final name = 'Bob';
final age = 25;
final items = [1, 2, 3];
// ✅ الأسلوب 2: تعليق صريح (مفضل لـ API)
String getName() => 'Bob';
int getAge() => 25;
List<int> getItems() => [1, 2, 3];
// ❌ تعليق زائد عن الحاجة
final String name2 = 'Bob'; // var name2 = 'Bob'; أفضل
(5) حالات خاصة: Collection literals
DART
// عند إنشاء List/Map/Set، يجب التعليق إذا كان النوع غير واضح
var numbers = <int>[1, 2, 3]; // تعليق ضروري — استنتاج <int>[]? لا
var map = <String, int>{'a': 1}; // تعليق ضروري
var set = <int>{1, 2, 3}; // تعليق ضروري
// من النوع الواضح من العناصر
var names = ['Alice', 'Bob']; // ✅ استنتاج List<String>
var ages = {'Alice': 30}; // ✅ استنتاج Map<String, int>
▶ مثال: مشروع حقيقي — فئة المستخدم
DART
class User {
// 1. خاصية الفئة — تعليق صريح
final String name;
final int age;
final List<String> hobbies;
// 2. مُنشئ — نوع المعلمات صريح
User({
required this.name,
required this.age,
required this.hobbies,
});
// 3. نوع الإرجاع — صريح
String getGreeting() => 'Hello, $name!';
// 4. متغير محلي — استنتاج
bool get isAdult {
final adult = age >= 18; // استنتاج bool
return adult;
}
}
TEXT
📖 للعرض فقط
> **الإخراج:** فئة User بسيطة مع 5 حقول (name/age/hobbies) — تُظهر اصطلاح Dart: التعليق الصريح على واجهة API، واستنتاج النوع للمتغيرات المحلية.
6. استيفاء السلاسل
(1) بناء جملة الاستيفاء
DART
final name = 'Bob';
final age = 25;
// 1. استيفاء متغير بسيط
String s1 = 'Hello, $name!'; // Hello, Bob!
// 2. استيفاء تعبير
String s2 = '1 + 2 = ${1 + 2}'; // 1 + 2 = 3
String s3 = 'age = ${age}'; // age = 25
// 3. الاستيفاء المتداخل
String s4 = '${'Hello, $name!'.toUpperCase()}'; // HELLO, BOB!
// 4. لا متغيرات — $ نفسها
String s5 = 'Price: \$100'; // Price: $100 (مع escape)
String s6 = r'Price: $100'; // Price: $100 (سلسلة خام)
(2) متعدد الأسطر
DART
// علامات الاقتباس الثلاثية — علامات الاقتباس المفردة (تسمح بالاستيفاء)
String name = 'Bob';
String multi = '''
Hello, $name!
This is a multi-line string.
Indentation is preserved.
''';
// علامات الاقتباس الثلاثية — علامات الاقتباس المزدوجة
String multi2 = """
Hello, $name!
This is a multi-line string.
""";
// سلسلة خام (r prefix) — تتجاهل أحرف الهروب
String regex = r'\d+\.\d+'; // \d+\.\d+
String windowsPath = r'C:\Users\Bob\file.txt';
// سلسلة خام متعددة الأسطر
String rawMulti = r'''
No \n escape
$variable is literal (not interpolated)
''';
▶ مثال: استيفاء السلسلة الواقعية
DART
// فئة سجل بسيطة
class Logger {
final String module;
final DateTime time;
Logger(this.module, [DateTime? time]) : time = time ?? DateTime.now();
void info(String message) {
print('[$time] [$module] INFO: $message');
}
void error(String message, [Object? error, StackTrace? stack]) {
print('[$time] [$module] ERROR: $message');
if (error != null) {
print(' خطأ: $error');
}
if (stack != null) {
print(' $stack');
}
}
}
void main() {
final logger = Logger('SalesPredict');
logger.info('بدء معالجة البيانات');
logger.error('فشل الاتصال بقاعدة البيانات', 'انتهت المهلة');
}
BASH
$ dart run
[2026-07-27 14:30:15.123] [SalesPredict] INFO: بدء معالجة البيانات
[2026-07-27 14:30:16.456] [SalesPredict] ERROR: فشل الاتصال بقاعدة البيانات
خطأ: انتهت المهلة
TEXT
📖 للعرض فقط
> **الإخراج:** السجل المنسق — استفد من استيفاء السلسلة لتجنب التحويل اليدوي إلى String.
(3) أنماط شائعة لاستيفاء السلسلة
DART
final name = 'Bob';
final age = 25;
final items = ['apple', 'banana'];
// رسائل السجل
print('[$name] العمر: $age');
print('العناصر: $items');
print('عدد العناصر: ${items.length}');
// JSON-like
final json = '{"name": "$name", "age": $age}'; // ❌ يفتقر إلى الاقتباس لنوع String
final correctJson = '{"name": "${name.toJson()}", "age": $age}'; // ✅
// رسائل الخطأ
try {
// ...
} catch (e) {
print('فشل في $name: $e');
}
// URL
final url = 'https://api.example.com/users/$id/posts/$postId';
(4) تحويل إلى String
DART
// toString() — معظم الأنواع لها تنفيذ افتراضي
print(1.toString()); // '1'
print(1.5.toString()); // '1.5'
print(true.toString()); // 'true'
print([1, 2, 3].toString()); // '[1, 2, 3]'
// تحويل مخصص — تجاوز toString()
class Point {
final int x, y;
Point(this.x, this.y);
@override
String toString() => 'Point($x, $y)';
}
print(Point(1, 2)); // 'Point(1, 2)' (تلقائي)
7. عوامل النوع
(1) عوامل التحويل
DART
// as — تحويل قسري (آمن للأنواع المتوافقة)
Object value = 'hello';
String s = value as String; // 'hello'
// int i = value as int; // ❌ TypeError في وقت التشغيل
// is — فحص النوع
if (value is String) {
print('length: ${value.length}');
}
// is! — فحص النوع غير المتطابق (نفي)
if (value is! int) {
print('ليس int');
}
(2) التحويل الآمن (as?)
DART
Object value = 'hello';
// as? — تحويل آمن، يُرجع null في حالة الفشل
String? s = value as? String; // 'hello'
int? i = value as? int; // null (بدلاً من رمي خطأ)
if (s != null) {
print('String length: ${s.length}');
}
(3) pattern matching (Dart 3)
DART
// switch التعبير مع مطابقة الأنماط
String describe(Object obj) => switch (obj) {
int n when n > 0 => 'int موجب $n',
int n => 'int $n',
String s => 'String "$s"',
List l when l.isEmpty => 'قائمة فارغة',
List l => 'قائمة بطول ${l.length}',
_ => 'unknown',
};
print(describe(42)); // 'int موجب 42'
print(describe('hello')); // 'String "hello"'
print(describe([1, 2])); // 'قائمة بطول 2'
(4) توافق الأنواع (Variance)
DART
// التباين المشترك — List<Dog> هي List<Animal> (للقراءة فقط)
List<Animal> animals = <Dog>[].cast<Animal>();
animals.add(Cat()); // ❌ خطأ — لا يمكن الكتابة
// التباين العكسي — List<Animal> هي List<Dog> (للقراءة فقط)
List<Dog> dogs = <Animal>[Dog()] as List<Dog>;
dogs.first.bark(); // ✅
// ثابت (Invariant) — النوع نفسه فقط
List<int> ints = [1, 2, 3];
// List<num> nums = ints; // ❌ خطأ — List<int> ليست List<num>
▶ مثال: فحص النوع + التحويل في مشروع
DART
// معالجة الاستجابة من API
Map<String, dynamic> response = {
'status': 'success',
'data': {
'id': 1,
'name': 'Bob',
'age': 25,
'tags': ['user', 'premium'],
},
};
// استخراج آمن من النوع
String status = response['status'] as String;
Map<String, dynamic>? data = response['data'] as Map<String, dynamic>?;
if (data != null) {
int id = data['id'] as int;
String name = data['name'] as String;
int? age = data['age'] as int?; // قد يكون null
// معالجة القائمة
List<dynamic>? tags = data['tags'] as List<dynamic>?;
if (tags != null) {
for (final tag in tags) {
print('tag: $tag');
}
}
}
TEXT
📖 للعرض فقط
> **الإخراج:** عند معالجة JSON، استخدم `as?` للسلامة، أو استخرج النوع بأمان. لا تستخدم `as!` أبدًا (ستفشل في وقت التشغيل إذا كان النوع غير صحيح).
(5) final vs const vs var — متى تستخدم أيهما
DART
// ✅ الإعداد الموصى به
class Config {
// final — خاصية غير قابلة للتغيير، تم التعيين في المُنشئ
final String name;
final int maxRetries;
// const — ثابت وقت الترجمة
static const defaultName = 'default';
static const defaultMaxRetries = 3;
// late — تهيئة كسولة
late final String computedConfig = _computeConfig();
Config({this.name = defaultName, this.maxRetries = defaultMaxRetries});
String _computeConfig() => '$name-$maxRetries';
}
void main() {
print(Config.defaultName); // 'default'
final config = Config();
print(config.computedConfig); // 'default-3'
}
8. ملخص نقاط المعرفة الأساسية لهذا الدرس
| # | المفهوم | المستوى | الأهمية |
|---|---|---|---|
| 1 | var / final / const / late / dynamic = 5 طرق إعلان | مبتدئ | ⭐⭐⭐⭐⭐ |
| 2 | int / double / num / String / bool / List / Map / Set / null | مبتدئ | ⭐⭐⭐⭐⭐ |
| 3 | استنتاج النوع vs التعليق الصريح | مبتدئ | ⭐⭐⭐⭐⭐ |
| 4 | String interpolation + multi-line + r prefix | مبتدئ | ⭐⭐⭐⭐ |
| 5 | فحص النوع is + التحويل الآمن as? | متوسط | ⭐⭐⭐⭐ |
| 6 | Late initialization | متوسط | ⭐⭐⭐ |
❓ أسئلة شائعة
س هل يجب استخدام
var أو التعليق الصريح في كل مكان؟ج القاعدة:
var للمتغيرات المحلية، التعليق الصريح لواجهات API (الفئات، الدوال، المعلمات). يفضل كود Dart الجيد 80% استنتاج و20% تعليق. الإفراط في التعليق يضيف ضوضاء، والإفراط في استنتاج يضر بقراءة API العام.س ما الفرق بين
final و const؟ج
const ثابت في وقت الترجمة (compile-time) — يجب أن تكون القيمة معروفة في وقت الترجمة. final ثابت في وقت التشغيل (runtime) — يمكن التعيين بأي قيمة في وقت التشغيل، بما في ذلك استدعاءات الدوال. عمليًا: const للتكوين والثوابت، final للمتغيرات التي لا تتغير بعد التعيين. const ضمني ضمن final، لكنهما ليسا متطابقين.س متى تستخدم
late؟ج ثلاثة سيناريوهات: ① تهيئة متأخرة لخصائص الفئة التي تتطلب وقتًا أو موارد ② قيم تعتمد على شروط وقت التشغيل ③ حل التبعيات الدائرية. ولكن تجنب الإفراط في الاستخدام —
late يخفي التهيئة، وقد يتسبب في أخطاء وقت التشغيل إذا لم تتم تهيئته قبل الوصول.س لماذا لا يوجد
int? افتراضي في Dart 3؟ج هذا هو أمان القيم الفارغة (Null Safety) من Dart 3. لا شيء يمكن أن يكون null افتراضيًا. إذا كنت بحاجة إلى متغير يمكن أن يكون null، يجب التصريح صراحةً بـ
? (مثل int?). هذا يمنع NullPointerException الكلاسيكي. لاحظ أن Dart 2.12+ بدأ تقديم هذا، Dart 3 يكمل التحول.س ما الفرق بين
List و const [] في Dart 3؟ج
List هو نوع، const [] هو ثابت. const [] ينشئ قائمة ثابتة غير قابلة للتغيير. final list = [] ينشئ متغيرًا غير قابل للتعيين، لكن القائمة نفسها قابلة للتغيير (list.add(4) مسموح). const list = [1, 2, 3] ينشئ قائمة ثابتة وغير قابلة للتغيير. للقراءة فقط، يفضل const لتحسين الأداء.س كيف أحول
String إلى int بأمان؟ج استخدم
int.tryParse:
DART
final str = '42';
final i = int.tryParse(str); // 42
final bad = int.tryParse('hello'); // null (بدلاً من خطأ)
📖 ملخص
- 5 طرق للإعلان:
var(استنتاج + قابل للتعيين) /final(غير قابل للتعيين) /const(ثابت وقت الترجمة) /late(تهيئة كسولة) /dynamic(تجاوز فحص النوع) - الأنواع المدمجة: int / double / num / String / bool + List / Map / Set
- استنتاج النوع: 80% من الكود، للمتغيرات المحلية
- التعليق الصريح: 20% من الكود، لواجهات API
- استيفاء السلسلة:
$varللاستيفاء البسيط،${expr}للتعبيرات،''' '''للأسطر المتعددة،rللسلسلة الخام - أمان القيم الفارغة: يجب التصريح بـ
?صراحةً، لا شيء افتراضي
📝 تمارين
- أساسي (الصعوبة ⭐): أنشئ مشروع
type_explorer، واكتب 5 متغيرات بأنواع مختلفة (String/int/double/bool/List<String>)، واختبر استخدام كل منvarوfinalوconstوlateوdynamic(5 طرق إعلان)، واستخدمdart analyzeللتحقق من أن جميع الإعلانات صحيحة. أنشئbin/test.dartمعmain()يطبع جميع المتغيرات. تلميح: استخدمdynamicبحذر — طباعةdynamic.runtimeTypeيساعد في رؤية النوع الفعلي. - متوسط (الصعوبة ⭐⭐): أنشئ مشروع
null_safety_practice، واكتب دالةparseUserالتي تأخذMap<String, dynamic>(مثل استجابة API) وتستخرج 5 حقول (id,name,age,email,tags)، باستخدامas?(التحويل الآمن) و??(المشغل الافتراضي) للتعامل مع الحقول المفقودة أو الخاطئة. اكتب 3 حالات اختبار: ① خريطة كاملة ② خريطة بأجزاء مفقودة ③ خريطة بأنواع خاطئة. استخدمdart testلاختبار الحالات. تلميح: استخدمMap<String, dynamic>.from()في الاختبارات لإنشاء خرائط اختبار. - تحدٍّ (الصعوبة ⭐⭐⭐): أنشئ مشروع
string_interpolation_masterيستكشف 5 أنماط لاستيفاء السلسلة: ① استيفاء متغير بسيط ($name) ② استيفاء تعبير (${1+2}) ③ استيفاء متداخل (داخل تعبير) ④ علامات اقتباس ثلاثية مع استيفاء ⑤ سلسلة خام (r prefix) مع أحرف الهروب والاستيفاء. أنشئbin/showcase.dartمع 5 دالات، كل منها يستكشف نمطًا، ويعرض الفرق بين الاستيفاء العادي والخام في نفس الوقت. على سبيل المثال:print(r'$var لا يستيفع، ولكن ${'$var'} يستيفع'). تلميح: سلسلة خام تتجاهل أحرف الهروب\nو\t، ولكنها لا تمنع$من العمل (لا يزال استيفاء).