Dart: مجموعات Dart — عمليات List / Set / Map عملياً

آخر تحديث: 2026-08-26

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

1. ما ستتعلمه


2. قصة مطور حقيقي

(1) نقطة الألم: التجميع اليدوي والحسابات الإجمالية مستهلك للوقت وعرضة للخطأ

يحتاج بوب إلى تجميع 1,200,000 طلب حسب الفئة، مع حساب إجمالي الإيرادات وعدد الطلبات لكل فئة. كتب منطق التجميع يدوياً بحلقة for، في 50 سطراً من الكود، لكن واجه مشاكل مثل استبدال المفتاح المكرر والفرز غير المستقر. استغرق معالجة 1,200,000 سجل 8 ثوانٍ.

(2) الحل: عمليات المجموعات

عمليات المجموعات في Dart مع الدوال عالية الرتبة تُختصر التجميع والحسابات إلى 10 أسطر فقط من الكود، مع تحسن في السرعة بنسبة 40%.

DART
// تجميع الطلبات حسب الفئة وحساب الإجمالي
final byCategory = <String, List<Order>>{};
for (final order in orders) {
  byCategory.putIfAbsent(order.category, () => []).add(order);
}

// أو استخدام fold للتجميع
final revenueByCategory = orders.fold<Map<String, double>>(
  {}, (acc, o) => acc..update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount));
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

(3) الفوائد


3. القائمة (List)

(1) الإنشاء والعمليات الأساسية

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: إنشاء القائمة

DART
void main() {
  // إنشاء حرفي
  List<int> counts = [1, 2, 3, 4, 5];
  var amounts = <double>[1500.0, 3200.0, 890.0];

  // قائمة قابلة للنمو
  var orders = <String>['ORD-001', 'ORD-002'];
  orders.add('ORD-003');

  // قائمة بطول ثابت
  var buffer = List<double>.filled(5, 0.0);
  buffer[0] = 100.0;

  // توليد قائمة
  var batch = List.generate(10, (i) => 'Batch-${i + 1}');

  print(counts);      // [1, 2, 3, 4, 5]
  print(orders);      // [ORD-001, ORD-002, ORD-003]
  print(buffer);      // [100.0, 0.0, 0.0, 0.0, 0.0]
  print(batch);       // [Batch-1, ..., Batch-10]
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: مرور القائمة وفرزها

DART
void main() {
  var amounts = [1500.0, 890.0, 3200.0, 50.0];

  // فرز في المكان
  amounts.sort();
  print(amounts);  // [50.0, 890.0, 1500.0, 3200.0]

  // فرز تنازلي (إنشاء قائمة جديدة)
  var descending = [...amounts]..sort((a, b) => b.compareTo(a));
  print(descending);  // [3200.0, 1500.0, 890.0, 50.0]

  // الوصول إلى العناصر
  print(amounts.first);  // 50.0
  print(amounts.last);   // 3200.0
  print(amounts[2]);     // 1500.0

  // قائمة فرعية
  print(amounts.sublist(1, 3));  // [890.0, 1500.0]
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: النشر و collection-if/for

DART
void main() {
  var base = ['ORD-001', 'ORD-002'];
  var extra = ['ORD-003', 'ORD-004'];

  // عامل النشر
  var combined = [...base, ...extra];
  print(combined);  // [ORD-001, ORD-002, ORD-003, ORD-004]

  // النشر مع التعامل مع null
  List<String>? maybeList;
  var safe = ['header', ...?maybeList, 'footer'];
  print(safe);  // [header, footer]

  // collection-if
  bool includePremium = true;
  var tiers = [
    'Standard',
    if (includePremium) 'Premium',
    'Enterprise',
  ];
  print(tiers);  // [Standard, Premium, Enterprise]

  // collection-for
  var batch = [
    for (int i = 1; i <= 3; i++) 'Batch-$i',
  ];
  print(batch);  // [Batch-1, Batch-2, Batch-3]
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.
العملية الصيغة الوصف
النشر ...list ينشر العناصر
النشر مع فارغ ...?list ينشر قائمة قابلة للـ فارغ
collection-if if (cond) expr تضمين مشروط
collection-for for (var x in list) expr توليد بحلقة

4. المجموعة (Set)

(1) الإنشاء وعمليات المجموعة

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: إزالة التكرار وعمليات المجموعة

DART
void main() {
  // الإنشاء من قائمة (يزيل التكرارات)
  var allOrders = ['ORD-001', 'ORD-002', 'ORD-001', 'ORD-003'];
  var uniqueOrders = <String>{...allOrders};
  print(uniqueOrders);  // {ORD-001, ORD-002, ORD-003}

  // عمليات المجموعة
  var setA = {1, 2, 3, 4, 5};
  var setB = {4, 5, 6, 7, 8};

  print(setA.intersection(setB));  // {4, 5} - التقاطع
  print(setA.union(setB));         // {1, 2, 3, 4, 5, 6, 7, 8} - الاتحاد
  print(setA.difference(setB));    // {1, 2, 3} - الفرق

  // اختبار العضوية
  print(setA.contains(3));    // true
  print(setA.containsAll({1, 2}));  // true
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: تطبيق المجموعة في سيناريوهات إزالة التكرار

DART
void main() {
  // إزالة تكرار الفئات من قائمة الطلبات
  var orders = [
    {'id': 'ORD-001', 'category': 'Electronics'},
    {'id': 'ORD-002', 'category': 'Books'},
    {'id': 'ORD-003', 'category': 'Electronics'},
    {'id': 'ORD-004', 'category': 'Clothing'},
    {'id': 'ORD-005', 'category': 'Books'},
  ];

  var categories = orders.map((o) => o['category'] as String).toSet();
  print(categories);  // {Electronics, Books, Clothing}
  print('Unique categories: ${categories.length}');  // 3
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.
العملية الطريقة النتيجة
التقاطع a.intersection(b) العناصر المشتركة
الاتحاد a.union(b) جميع العناصر
الفرق a.difference(b) عناصر a غير الموجودة في b
الاحتواء a.contains(e) bool
احتواء الكل a.containsAll(b) bool

5. الخريطة (Map)

(1) الإنشاء والعمليات

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: إنشاء الخريطة والعمليات الأساسية

DART
void main() {
  // إنشاء حرفي
  var config = <String, dynamic>{
    'appName': 'DataPipeline',
    'maxRecords': 1000000,
    'taxRate': 0.08,
    'verbose': true,
  };

  // الوصول إلى القيم
  print(config['appName']);       // DataPipeline
  print(config['missing']);       // null
  print(config['missing'] ?? 'N/A');  // N/A

  // إضافة/تحديث الإدخالات
  config['outputPath'] = '/tmp/reports';  // إضافة
  config['taxRate'] = 0.10;               // تحديث

  // تحديث آمن
  config.update('maxRecords', (v) => v * 2, ifAbsent: () => 500000);

  // التكرار
  config.forEach((key, value) => print('  $key: $value'));
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: تكرار الخريطة والتجميع

DART
void main() {
  var orders = [
    {'id': 'ORD-001', 'category': 'Electronics', 'amount': 1500.0},
    {'id': 'ORD-002', 'category': 'Books', 'amount': 50.0},
    {'id': 'ORD-003', 'category': 'Electronics', 'amount': 3200.0},
    {'id': 'ORD-004', 'category': 'Clothing', 'amount': 890.0},
    {'id': 'ORD-005', 'category': 'Books', 'amount': 120.0},
  ];

  // التجميع حسب الفئة
  final byCategory = <String, List<Map<String, dynamic>>>{};
  for (final order in orders) {
    final cat = order['category'] as String;
    byCategory.putIfAbsent(cat, () => []).add(order);
  }

  // تجميع الإيرادات حسب الفئة
  final revenue = <String, double>{};
  for (final entry in byCategory.entries) {
    final total = entry.value.fold<double>(
      0, (sum, o) => sum + (o['amount'] as double));
    revenue[entry.key] = total;
  }

  // طباعة النتائج
  revenue.forEach((cat, total) =>
      print('$cat: \$${total.toStringAsFixed(2)} USD'));
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

6. استنتاج نوع المجموعات والمجموعات الثابتة (const)

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: استنتاج النوع والمجموعات الثابتة

DART
void main() {
  // استنتاج النوع
  var list1 = [1, 2, 3];           // List<int>
  var list2 = [1, 2, 3.0];         // List<num> (مزيج من int و double)
  var list3 = <String>[];          // نوع عام صريح للقائمة الفارغة

  // مجموعات ثابتة - ثابتة بعمق
  const categories = ['Electronics', 'Books', 'Clothing'];
  const config = <String, int>{'batchSize': 10000, 'timeout': 30};
  const flags = {true, false};

  // categories.add('Sports');  // خطأ! لا يمكن تعديل const

  // const داخل الفئة
  const defaultFormats = ['json', 'csv', 'html'];
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.
الميزة var final const
قابلية تعديل المحتوى نعم نعم (مرجع ثابت) لا
استنتاج النوع تلقائي تلقائي تلقائي
ثابت وقت الترجمة لا لا نعم

7. سيناريو بوب: تجميع ملايين الطلبات وحساب إجمالياتها

100%
flowchart LR
  A[قائمة: 1.2 مليون طلب] --> B[where: تصفية]
  B --> C[groupBy: الفئة]
  C --> D[Map: الفئة ← الطلبات]
  D --> E[fold: تجميع]
  E --> F[الإخراج: الإحصائيات]
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

: التجميع والحساب الإجمالي عملياً

DART
class Order {
  final String id;
  final String category;
  final double amount;
  final String status;

  Order({required this.id, required this.category, required this.amount, required this.status});
}

Map<String, Map<String, dynamic>> analyzeOrders(List<Order> orders) {
  // تصفية الطلبات المكتملة
  final completed = orders.where((o) => o.status == 'completed').toList();

  // التجميع حسب الفئة
  final grouped = <String, List<Order>>{};
  for (final order in completed) {
    grouped.putIfAbsent(order.category, () => []).add(order);
  }

  // تجميع الإجماليات لكل فئة
  final result = <String, Map<String, dynamic>>{};
  for (final entry in grouped.entries) {
    final total = entry.value.fold<double>(0, (s, o) => s + o.amount);
    final avg = total / entry.value.length;
    result[entry.key] = {
      'count': entry.value.length,
      'total': total,
      'average': avg,
    };
  }
  return result;
}

void main() {
  final orders = [
    Order(id: 'ORD-001', category: 'Electronics', amount: 1500.0, status: 'completed'),
    Order(id: 'ORD-002', category: 'Books', amount: 50.0, status: 'completed'),
    Order(id: 'ORD-003', category: 'Electronics', amount: 3200.0, status: 'pending'),
    Order(id: 'ORD-004', category: 'Clothing', amount: 890.0, status: 'completed'),
    Order(id: 'ORD-005', category: 'Books', amount: 120.0, status: 'completed'),
  ];

  final analysis = analyzeOrders(orders);
  print('=== Category Analysis ===');
  for (final entry in analysis.entries) {
    final stats = entry.value;
    print('${entry.key}:');
    print('  Orders:  ${stats['count']}');
    print('  Total:   \$${(stats['total'] as double).toStringAsFixed(2)} USD');
    print('  Average: \$${(stats['average'] as double).toStringAsFixed(2)} USD');
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

8. المثال الكامل: محلل مجموعات خط أنابيب البيانات

DART
// ============================================
// محلل مجموعات خط أنابيب البيانات
// عرض كامل لعمليات List و Set و Map
// ============================================

class Order {
  final String id;
  final String category;
  final double amount;
  final String status;
  final String region;

  const Order({
    required this.id,
    required this.category,
    required this.amount,
    required this.status,
    this.region = 'US',
  });
}

class OrderAnalyzer {
  final List<Order> orders;

  OrderAnalyzer(this.orders);

  // الفئات الفريدة (Set)
  Set<String> get categories => orders.map((o) => o.category).toSet();

  // المناطق الفريدة (Set)
  Set<String> get regions => orders.map((o) => o.region).toSet();

  // الطلبات المكتملة (List filter)
  List<Order> get completed =>
      orders.where((o) => o.status == 'completed').toList();

  // إجمالي الإيرادات (fold)
  double get totalRevenue =>
      completed.fold(0.0, (sum, o) => sum + o.amount);

  // الإيرادات حسب الفئة (Map grouping)
  Map<String, double> get revenueByCategory {
    return completed.fold<Map<String, double>>({}, (acc, o) {
      acc.update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount);
      return acc;
    });
  }

  // عدد الطلبات حسب المنطقة والحالة
  Map<String, Map<String, int>> get countByRegionStatus {
    final result = <String, Map<String, int>>{};
    for (final order in orders) {
      result.putIfAbsent(order.region, () => {});
      result[order.region]!.update(
        order.status, (v) => v + 1, ifAbsent: () => 1);
    }
    return result;
  }

  // أعلى N فئات حسب الإيرادات
  List<MapEntry<String, double>> topCategories(int n) {
    final sorted = revenueByCategory.entries.toList()
      ..sort((a, b) => b.value.compareTo(a.value));
    return sorted.take(n).toList();
  }

  void printReport() {
    print('=== DataPipeline Analytics Report ===');
    print('Total orders:    ${orders.length}');
    print('Completed:       ${completed.length}');
    print('Unique categories: ${categories.length}');
    print('Regions:         ${regions}');
    print('Total revenue:   \$${totalRevenue.toStringAsFixed(2)} USD');

    print('\n--- Revenue by Category ---');
    for (final entry in revenueByCategory.entries) {
      print('  ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
    }

    print('\n--- Top Categories ---');
    for (final entry in topCategories(3)) {
      print('  ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
    }

    print('\n--- Orders by Region & Status ---');
    for (final region in countByRegionStatus.entries) {
      print('  ${region.key}: ${region.value}');
    }
  }
}

void main() {
  final orders = [
    Order(id: 'ORD-001', category: 'Electronics', amount: 1500.0, status: 'completed', region: 'US'),
    Order(id: 'ORD-002', category: 'Books', amount: 50.0, status: 'completed', region: 'EU'),
    Order(id: 'ORD-003', category: 'Electronics', amount: 3200.0, status: 'pending', region: 'US'),
    Order(id: 'ORD-004', category: 'Clothing', amount: 890.0, status: 'completed', region: 'EU'),
    Order(id: 'ORD-005', category: 'Books', amount: 120.0, status: 'completed', region: 'US'),
    Order(id: 'ORD-006', category: 'Electronics', amount: 2100.0, status: 'completed', region: 'AP'),
  ];

  OrderAnalyzer(orders).printReport();
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً حسب إصدار SDK.

الإخراج:

TEXT 📖 للعرض فقط
=== DataPipeline Analytics Report ===
Total orders:    6
Completed:       5
Unique categories: 3
Regions:         {US, EU, AP}
Total revenue:   $4660.00 USD

--- Revenue by Category ---
  Electronics: $3600.00 USD
  Clothing: $890.00 USD
  Books: $170.00 USD

--- Top Categories ---
  Electronics: $3600.00 USD
  Clothing: $890.00 USD
  Books: $170.00 USD

--- Orders by Region & Status ---
  US: {completed: 2, pending: 1}
  EU: {completed: 2}
  AP: {completed: 1}

❓ أسئلة شائعة

س: ما الفرق في الأداء بين List و Set؟ ج: القائمة تخزن العناصر حسب ترتيب الإدراج، مع وصول O(1) بالفهرس وبحث O(n). المجموعة مبنية على التجزئة، وتوفر بحث/احتواء O(1) لكن بدون وصول بالفهرس. استخدم المجموعة لسيناريوهات إزالة التكرار، والقائمة عندما يهم الترتيب.

س: هل يمكن أن تكون مفاتيح الخريطة من أي نوع؟ ج: نعم، لكن عند استخدام فئات مخصصة كمفاتيح، يجب عليك تجاوز == و hashCode؛ وإلا فلن تتمكن الخريطة من العثور على الإدخالات بشكل صحيح. يُنصح باستخدام String أو int كمفاتيح.

س: هل أستخدم [] أم <Type>[] للقائمة الفارغة؟ ج: استخدم [] عندما يكون سياق استنتاج النوع متاحاً. استخدم <Type>[] عند الإعلان بشكل مستقل لتجنب الاستنتاج كـ List<dynamic>.

س: هل يمكن استخدام collection-if و collection-for فقط في القيم الحرفية للقوائم؟ ج: لا، القيم الحرفية للقوائم والمجموعات والخرائط كلها تدعم collection-if و collection-for. على سبيل المثال: {for (var i in items) i.name: i.value}.

س: ما الفرق بين putIfAbsent و update في الخريطة؟ ج: putIfAbsent تعين القيمة فقط إذا لم يكن المفتاح موجوداً. update تحدّث القيمة فقط إذا كان المفتاح موجوداً (استخدم المعامل ifAbsent للتعامل مع حالة عدم الوجود).

س: ما الفرق بين المجموعات الثابتة (const) والمجموعات النهائية (final)؟ ج: المجموعة الثابتة هي ثابت وقت الترجمة، بمحتوى غير قابل للتعديل بالكامل (ثبات عميق). المجموعة النهائية لها مرجع غير قابل للتعديل، لكن المحتوى يمكن أن يكون قابلاً للتعديل (مثلاً، يمكن إضافة عناصر إلى List نهائية).

س: كيف يمكنني فرز قائمة كبيرة بكفاءة؟ ج: List.sort() تفرز في المكان، وهي أكثر كفاءة من [...list].sort() (التي تنشئ نسخة). إذا كنت لا تريد تعديل القائمة الأصلية، أنشئ نسخة أولاً باستخدام [...list] ثم فرز.


📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): أنشئ قائمة تحتوي على 10 مبالغ طلبات. استخدم where لتصفية المبالغ الأكبر من 500، و map لتنسيقها بالدولار الأمريكي، و reduce لحساب المجموع الكلي.
  2. متوسط (صعوبة ⭐⭐): بالنظر إلى مجموعة من بيانات الطلبات، استخدم putIfAbsent و fold في الخريطة لتجميع الطلبات حسب الفئة، مع حساب عدد الطلبات وإجمالي الإيرادات لكل فئة، وإخراج تقرير منسق.
  3. تحدي (صعوبة ⭐⭐⭐): نفّذ فئة CollectionPipeline تدعم عمليات متسلسلة مثل where و map و sortBy و groupBy و aggregate، وتُخرج في النهاية نتائج التحليل. تلميح: كل خطوة تُرجع مجموعة جديدة أو خريطة جديدة.

← الدرس السابق | الدرس التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%