در الگوها و رکوردهای دارت شیرجه بزنید

1. مقدمه

دارت 3 الگوهایی را به زبان معرفی می کند، مقوله جدیدی از دستور زبان. فراتر از این روش جدید برای نوشتن کد دارت، چندین پیشرفت زبان دیگر نیز وجود دارد، از جمله

  • سوابق برای دسته بندی داده ها از انواع مختلف،
  • اصلاح کننده های کلاس برای کنترل دسترسی، و
  • عبارات سوئیچ جدید و عبارات if-case .

این ویژگی ها انتخاب های شما را هنگام نوشتن کد دارت گسترش می دهند. در این لبه کد، یاد می گیرید که چگونه از آنها برای فشرده تر، ساده تر و انعطاف پذیرتر کردن کد خود استفاده کنید.

این کد لبه فرض می کند که شما با فلاتر و دارت آشنایی دارید. اگر احساس می‌کنید کمی زنگ زده‌اید، اصول اولیه را با منابع زیر در نظر بگیرید:

چیزی که خواهی ساخت

این Codelab برنامه ای ایجاد می کند که یک سند JSON را در Flutter نمایش می دهد. این برنامه JSON را شبیه سازی می کند که از یک منبع خارجی می آید. JSON حاوی داده های سند مانند تاریخ اصلاح، عنوان، سرصفحه ها و پاراگراف ها است. شما کدی را می نویسید تا داده ها را به طور منظم در پرونده ها بسته بندی کنید تا بتوان آنها را در هر جایی که ویجت های فلاتر شما به آن نیاز دارند منتقل و باز کرد.

سپس از الگوها برای ساخت ویجت مناسب زمانی که مقدار با آن الگو مطابقت دارد استفاده می کنید. همچنین نحوه استفاده از الگوها برای تخریب داده ها به متغیرهای محلی را مشاهده می کنید.

برنامه نهایی که در این کد لبه می سازید، یک سند با عنوان، آخرین تاریخ اصلاح، سرصفحه ها و پاراگراف ها است.

چیزی که یاد خواهید گرفت

  • نحوه ایجاد رکوردی که چندین مقدار را با انواع مختلف ذخیره می کند.
  • نحوه برگرداندن چندین مقدار از یک تابع با استفاده از یک رکورد
  • نحوه استفاده از الگوها برای تطبیق، اعتبارسنجی و تخریب داده ها از سوابق و اشیاء دیگر.
  • نحوه اتصال مقادیر مطابق الگو به متغیرهای جدید یا موجود.
  • نحوه استفاده از قابلیت های جدید دستور سوئیچ، عبارات سوئیچ و دستورات if-case.
  • چگونه می توان از بررسی جامع بودن استفاده کرد تا اطمینان حاصل شود که هر مورد در یک عبارت سوئیچ یا عبارت سوئیچ رسیدگی می شود.

2. محیط خود را تنظیم کنید

  1. Flutter SDK را نصب کنید.
  2. یک ویرایشگر مانند Visual Studio Code (VS Code) راه اندازی کنید .
  3. مراحل تنظیم پلتفرم را برای حداقل یک پلتفرم هدف (iOS، Android، Desktop یا یک مرورگر وب) طی کنید.

3. پروژه را ایجاد کنید

قبل از غواصی در الگوها، رکوردها و سایر ویژگی های جدید، لحظه ای را صرف ایجاد یک پروژه Flutter ساده کنید که تمام کدهای خود را برای آن بنویسید.

یک پروژه فلاتر ایجاد کنید

  1. از دستور flutter create برای ایجاد یک پروژه جدید به نام patterns_codelab استفاده کنید. پرچم --empty از ایجاد برنامه پیشخوان استاندارد در فایل lib/main.dart جلوگیری می کند که به هر حال باید آن را حذف کنید.
flutter create --empty patterns_codelab
  1. سپس، دایرکتوری patterns_codelab با استفاده از VS Code باز کنید.
code patterns_codelab

یک اسکرین شات از VS Code که پروژه ایجاد شده با دستور "فلاتر ایجاد" را نمایش می دهد.

حداقل نسخه SDK را تنظیم کنید

  • محدودیت نسخه SDK را برای پروژه خود تنظیم کنید تا به Dart 3 یا بالاتر بستگی داشته باشد.

pubspec.yaml

environment:
  sdk: ^3.0.0

4. پروژه را راه اندازی کنید

در این مرحله شما دو فایل Dart را ایجاد یا به روز می کنید:

  • فایل main.dart که حاوی ویجت های برنامه است و
  • فایل data.dart که داده های برنامه را ارائه می کند.

در مراحل بعدی به اصلاح هر دوی این فایل ها ادامه خواهید داد.

داده های برنامه را تعریف کنید

  • یک فایل جدید به lib/data.dart ایجاد کنید و کد زیر را به آن اضافه کنید:

lib/data.dart

import 'dart:convert';

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);
}

const documentJson = '''
{
  "metadata": {
    "title": "My Document",
    "modified": "2023-05-10"
  },
  "blocks": [
    {
      "type": "h1",
      "text": "Chapter 1"
    },
    {
      "type": "p",
      "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
    },
    {
      "type": "checkbox",
      "checked": false,
      "text": "Learn Dart 3"
    }
  ]
}
''';

برنامه‌ای را تصور کنید که داده‌ها را از یک منبع خارجی دریافت می‌کند، مانند یک جریان ورودی/خروجی یا درخواست HTTP. در این لبه کد، شما با تمسخر داده‌های JSON ورودی با یک رشته چند خطی در متغیر documentJson ، آن مورد استفاده واقعی‌تر را ساده می‌کنید.

داده های JSON در کلاس Document تعریف شده است. بعداً در این لبه کد، توابعی را اضافه می کنید که داده ها را از JSON تجزیه شده برمی گرداند. این کلاس فیلد _json را در سازنده خود تعریف و مقداردهی اولیه می کند.

برنامه را اجرا کنید

دستور flutter create فایل lib/main.dart را به عنوان بخشی از ساختار فایل Flutter پیش فرض ایجاد می کند.

  1. برای ایجاد نقطه شروع برای برنامه، محتوای main.dart را با کد زیر جایگزین کنید:

lib/main.dart

import 'package:flutter/material.dart';

import 'data.dart';

void main() {
  runApp(const DocumentApp());
}

class DocumentApp extends StatelessWidget {
  const DocumentApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: DocumentScreen(
        document: Document(),
      ),
    );
  }
}

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Title goes here'),
      ),
      body: const Column(
        children: [
          Center(
            child: Text('Body goes here'),
          ),
        ],
      ),
    );
  }
}

شما دو ویجت زیر را به برنامه اضافه کردید:

  • DocumentApp آخرین نسخه طراحی متریال را برای قالب بندی رابط کاربری راه اندازی می کند.
  • DocumentScreen طرح بصری صفحه را با استفاده از ویجت Scaffold ارائه می دهد.
  1. برای اطمینان از اینکه همه چیز به خوبی اجرا می شود، برنامه را در دستگاه میزبان خود با کلیک روی Run and Debug اجرا کنید:

تصویری از دکمه «اجرا و اشکال‌زدایی»، موجود در بخش «اجرا و اشکال‌زدایی» نوار فعالیت در سمت چپ.

  1. به‌طور پیش‌فرض، Flutter هر پلتفرم هدفی را که در دسترس باشد انتخاب می‌کند. برای تغییر پلتفرم هدف، پلتفرم فعلی را در نوار وضعیت انتخاب کنید:

تصویری از انتخابگر پلتفرم هدف در VS Code.

شما باید یک قاب خالی با title و عناصر body تعریف شده در ویجت DocumentScreen ببینید:

اسکرین شات از اپلیکیشن ساخته شده در این مرحله.

5. ایجاد و بازگرداندن سوابق

در این مرحله از رکوردها برای برگرداندن چندین مقدار از یک فراخوانی تابع استفاده می کنید. سپس، آن تابع را در ویجت DocumentScreen فراخوانی می کنید تا به مقادیر دسترسی داشته باشید و آنها را در رابط کاربری منعکس کنید.

یک رکورد ایجاد و برگردانید

  • در data.dart ، یک متد getter جدید به کلاس Document به نام metadata اضافه کنید که یک رکورد را برمی‌گرداند:

lib/data.dart

import 'dart:convert';

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);

  (String, {DateTime modified}) get metadata {           // Add from here...
    const title = 'My Document';
    final now = DateTime.now();

    return (title, modified: now);
  }                                                      // to here.
}

نوع برگشتی برای این تابع یک رکورد با دو فیلد است، یکی با نوع String و دیگری با نوع DateTime .

دستور return با قرار دادن دو مقدار داخل پرانتز، یک رکورد جدید می سازد (title, modified: now) .

فیلد اول موقعیتی و بدون نام است و فیلد دوم modified نام دارد.

دسترسی به فیلدهای رکورد

  1. در ویجت DocumentScreen ، متد فراداده metadata را در متد build فراخوانی کنید تا بتوانید رکورد خود را دریافت کرده و به مقادیر آن دسترسی داشته باشید:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final metadataRecord = document.metadata;              // Add this line.

    return Scaffold(
      appBar: AppBar(
        title: Text(metadataRecord.$1),                    // Modify this line,
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified ${metadataRecord.modified}',  // And this one.
            ),
          ),
        ],
      ),
    );
  }
}

متد فراداده metadata رکوردی را برمی‌گرداند که به متغیر محلی metadataRecord اختصاص داده می‌شود. رکوردها روشی سبک و آسان برای برگرداندن چندین مقدار از یک فراخوانی یک تابع و تخصیص آنها به یک متغیر است.

برای دسترسی به فیلدهای جداگانه تشکیل شده در آن رکورد، می توانید از نحو گیرنده داخلی رکوردها استفاده کنید.

  • برای به دست آوردن یک فیلد موقعیتی (یک فیلد بدون نام، مانند title )، از دریافت کننده $<num> در رکورد استفاده کنید. این فقط فیلدهای بدون نام را برمی گرداند.
  • فیلدهای نامگذاری شده مانند modified دریافت کننده موقعیتی ندارند، بنابراین می توانید مستقیماً از نام آن مانند metadataRecord.modified استفاده کنید.

برای تعیین نام یک گیرنده برای یک فیلد موقعیتی، از $1 شروع کنید و از فیلدهای نامگذاری شده رد شوید. به عنوان مثال:

var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1);                               // prints y
print(record.$2);                               // prints z
  1. برای مشاهده مقادیر JSON نمایش داده شده در برنامه، دوباره بارگیری کنید. پلاگین VS Code Dart هر بار که فایلی را ذخیره می کنید، دوباره بارگیری می شود.

تصویری از برنامه که عنوان و تاریخ اصلاح را نشان می دهد.

می بینید که هر فیلد در واقع نوع خود را حفظ کرده است.

  • متد Text() یک رشته را به عنوان اولین آرگومان خود می گیرد.
  • فیلد modified DateTime است و با استفاده از درون یابی رشته ای به String تبدیل می شود.

روش دیگر برای بازگرداندن انواع مختلف داده، تعریف کلاس است که پرمخاطب تر است.

6. مطابقت و تخریب با الگوها

رکوردها می توانند به طور موثر انواع مختلف داده ها را جمع آوری کرده و به راحتی آن ها را منتقل کنند. اکنون، کد خود را با استفاده از الگوها بهبود دهید.

یک الگو ساختاری را نشان می دهد که یک یا چند مقدار می تواند داشته باشد، مانند یک نقشه. الگوها با مقادیر واقعی مقایسه می شوند تا مشخص شود که آیا مطابقت دارند یا خیر.

برخی از الگوها، هنگامی که با هم مطابقت دارند، با بیرون کشیدن داده ها از آن، مقدار منطبق را تخریب می کنند . Destructuring به شما امکان می دهد مقادیر را از یک شی باز کنید تا آنها را به متغیرهای محلی اختصاص دهید یا تطبیق بیشتری روی آنها انجام دهید.

یک رکورد را به متغیرهای محلی تخریب کنید

  1. متد build DocumentScreen را برای metadata فراداده و استفاده از آن برای مقداردهی اولیه اعلان متغیر الگوی تغییر دهید:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, modified: modified) = document.metadata;   // Modify

    return Scaffold(
      appBar: AppBar(
        title: Text(title),                                  // Modify
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified $modified',                     // Modify
            ),
          ),
        ],
      ),
    );
  }
}

الگوی رکورد (title, modified: modified) شامل دو الگوی متغیر است که با فیلدهای رکورد برگردانده شده توسط metadata مطابقت دارند.

  • عبارت با الگوی فرعی مطابقت دارد زیرا نتیجه یک رکورد با دو فیلد است که یکی از آنها modified نام دارد.
  • از آنجایی که آنها مطابقت دارند، الگوی اعلان متغیر، عبارت را تخریب می کند، به مقادیر آن دسترسی پیدا می کند و آنها را به متغیرهای محلی جدید با همان نوع و نام، String title و DateTime modified دهد.

زمانی که نام یک فیلد و متغیری که آن را پر می کند یکسان است، مختصر وجود دارد. روش build DocumentScreen را به صورت زیر Refactor کنید.

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;            // Modify

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified $modified',
            ),
          ),
        ],
      ),
    );
  }
}

نحو الگوی متغیر :modified مخفف modified: modified است. اگر یک متغیر محلی جدید با نام دیگری می‌خواهید، می‌توانید به جای آن modified: localModified را بنویسید.

  1. Hot Reload برای مشاهده نتیجه مشابه در مرحله قبل. رفتار دقیقاً مشابه است. شما فقط کد خود را مختصرتر کردید.

7. از الگوها برای استخراج داده ها استفاده کنید

در زمینه‌های خاص، الگوها نه تنها با هم مطابقت دارند و ساختارشان را تخریب می‌کنند، بلکه می‌توانند بر اساس مطابقت یا عدم تطابق الگو در مورد اینکه کد چه کاری انجام می‌دهد ، تصمیم بگیرند. به این الگوهای ابطال پذیر می گویند.

الگوی اعلان متغیری که در مرحله آخر استفاده کردید یک الگوی انکارناپذیر است: مقدار باید با الگو مطابقت داشته باشد وگرنه یک خطا است و تخریب صورت نمی‌گیرد. به هر اعلان یا تخصیص متغیری فکر کنید. شما نمی توانید مقداری را به یک متغیر اختصاص دهید اگر از نوع یکسان نباشد.

از سوی دیگر، الگوهای ابطال پذیر در زمینه های جریان کنترل استفاده می شود:

  • آنها انتظار دارند که برخی از ارزش هایی که با آنها مقایسه می کنند مطابقت نداشته باشند.
  • آنها برای تأثیرگذاری بر جریان کنترل ، بر اساس مطابقت یا عدم تطابق مقدار، قرار دارند.
  • اگر مطابقت نداشتند ، اجرا را با خطا قطع نمی‌کنند ، فقط به دستور بعدی می‌روند.
  • آنها می توانند متغیرهایی را که فقط زمانی قابل استفاده هستند که مطابقت داشته باشند را تخریب و متصل کنند

مقادیر JSON را بدون الگو بخوانید

در این بخش، داده‌ها را بدون تطابق الگو می‌خوانید تا ببینید الگوها چگونه می‌توانند به شما در کار با داده‌های JSON کمک کنند.

  • نسخه قبلی metadata با نسخه ای که مقادیر را از نقشه _json می خواند جایگزین کنید. این نسخه از metadata را در کلاس Document کپی و جایگذاری کنید:

lib/data.dart

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);

  (String, {DateTime modified}) get metadata {
    if (_json.containsKey('metadata')) {                     // Modify from here...
      final metadataJson = _json['metadata'];
      if (metadataJson is Map) {
        final title = metadataJson['title'] as String;
        final localModified =
            DateTime.parse(metadataJson['modified'] as String);
        return (title, modified: localModified);
      }
    }
    throw const FormatException('Unexpected JSON');          // to here.
  }
}

این کد تأیید می کند که داده ها بدون استفاده از الگوها به درستی ساختار یافته اند. در مرحله بعد، از تطبیق الگو برای انجام همان اعتبارسنجی با استفاده از کد کمتر استفاده می کنید. قبل از انجام هر کار دیگری سه بررسی را انجام می دهد:

  • JSON شامل ساختار داده ای است که شما انتظار دارید: if (_json.containsKey('metadata'))
  • داده ها دارای نوع مورد انتظار شما هستند: if (metadataJson is Map)
  • این که داده ها تهی نیستند ، که به طور ضمنی در بررسی قبلی تأیید شده است.

مقادیر JSON را با استفاده از الگوی نقشه بخوانید

با یک الگوی قابل رد، می توانید با استفاده از الگوی نقشه بررسی کنید که JSON دارای ساختار مورد انتظار است.

  • نسخه قبلی metadata را با این کد جایگزین کنید:

lib/data.dart

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);

  (String, {DateTime modified}) get metadata {
    if (_json                                                // Modify from here...
        case {
          'metadata': {
            'title': String title,
            'modified': String localModified,
          }
        }) {
      return (title, modified: DateTime.parse(localModified));
    } else {
      throw const FormatException('Unexpected JSON');
    }                                                        // to here.
  }
}

در اینجا، نوع جدیدی از if-statement (که در Dart 3 معرفی شده است)، if-case را مشاهده می کنید. بدنه case فقط در صورتی اجرا می شود که الگوی case با داده های _json مطابقت داشته باشد. این تطابق همان بررسی‌هایی را انجام می‌دهد که در نسخه اول metadata برای تأیید اعتبار JSON ورودی نوشتید. این کد موارد زیر را تایید می کند:

  • _json یک نوع Map است.
  • _json حاوی یک کلید metadata است.
  • _json null نیست.
  • _json['metadata'] نیز یک نوع نقشه است.
  • _json['metadata'] حاوی title کلید و modified .
  • title و localModified رشته هایی هستند و پوچ نیستند.

اگر مقدار مطابقت نداشته باشد، الگو رد می‌شود (از ادامه اجرا خودداری می‌کند) و به عبارت else می‌رود. اگر تطابق موفقیت آمیز باشد، الگو مقادیر title و modified را از نقشه تخریب می کند و آنها را به متغیرهای محلی جدید متصل می کند.

برای لیست کامل الگوها، جدول را در بخش الگوهای مشخصات ویژگی ببینید.

8. برنامه را برای الگوهای بیشتر آماده کنید

تا اینجای کار، به بخش metadata داده های JSON پرداخته اید. در این مرحله، منطق کسب‌وکار خود را کمی بیشتر اصلاح می‌کنید تا داده‌های موجود در فهرست blocks را مدیریت کرده و آن‌ها را در برنامه خود ارائه دهید.

{
  "metadata": {
    // ...
  },
  "blocks": [
    {
      "type": "h1",
      "text": "Chapter 1"
    },
    // ...
  ]
}

کلاسی ایجاد کنید که داده ها را ذخیره کند

  • یک کلاس جدید، Block ، به data.dart اضافه کنید، که برای خواندن و ذخیره داده‌های یکی از بلوک‌های موجود در داده‌های JSON استفاده می‌شود.

lib/data.dart

class Block {
  final String type;
  final String text;
  Block(this.type, this.text);

  factory Block.fromJson(Map<String, dynamic> json) {
    if (json case {'type': final type, 'text': final text}) {
      return Block(type, text);
    } else {
      throw const FormatException('Unexpected JSON format');
    }
  }
}

سازنده کارخانه fromJson() از همان if-case با الگوی نقشه استفاده می کند که قبلاً دیده اید.

توجه داشته باشید که json با الگوی نقشه مطابقت دارد، حتی اگر یکی از کلیدهای checked در الگو در نظر گرفته نشده باشد. الگوهای نقشه هر ورودی را در شیء نقشه که به صراحت در الگو لحاظ نشده است را نادیده می گیرند.

لیستی از اشیاء Block را برگردانید

  • سپس یک تابع جدید به getBlocks() را به کلاس Document اضافه کنید. getBlocks() JSON را به نمونه هایی از کلاس Block تجزیه می کند و لیستی از بلوک ها را برای رندر در رابط کاربری شما برمی گرداند:

lib/data.dart

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);

  (String, {DateTime modified}) get metadata {
    if (_json
        case {
          'metadata': {
            'title': String title,
            'modified': String localModified,
          }
        }) {
      return (title, modified: DateTime.parse(localModified));
    } else {
      throw const FormatException('Unexpected JSON');
    }
  }

  List<Block> getBlocks() {                                  // Add from here...
    if (_json case {'blocks': List blocksJson}) {
      return [for (final blockJson in blocksJson) Block.fromJson(blockJson)];
    } else {
      throw const FormatException('Unexpected JSON format');
    }
  }                                                          // to here.
}

تابع getBlocks() لیستی از اشیاء Block را برمی‌گرداند که بعداً از آنها برای ساختن رابط کاربری استفاده می‌کنید. یک دستور if-case آشنا، اعتبارسنجی را انجام می‌دهد و مقدار ابرداده blocks را به List جدیدی به نام blocksJson می‌فرستد (بدون الگو، برای ارسال به روش toList() نیاز دارید).

لیست تحت اللفظی شامل مجموعه ای برای به منظور پر کردن لیست جدید با اشیاء Block است.

این بخش هیچ ویژگی مرتبط با الگو را که قبلاً در این Codelab امتحان نکرده اید، معرفی نمی کند. در مرحله بعد، شما آماده می شوید تا موارد لیست را در UI خود رندر کنید.

9. از الگوها برای نمایش سند استفاده کنید

اکنون با استفاده از عبارت if-case و الگوهای ابطال‌پذیر، داده‌های JSON خود را با موفقیت تخریب و دوباره ترکیب می‌کنید. اما if-case تنها یکی از پیشرفت‌ها برای کنترل ساختارهای جریان است که با الگوها همراه است. اکنون، دانش خود را در مورد الگوهای ابطال پذیر برای تغییر عبارات به کار می گیرید.

آنچه را که با استفاده از الگوها با دستورهای سوئیچ ارائه می شود، کنترل کنید

  • در main.dart ، یک ویجت جدید به نام BlockWidget ایجاد کنید که استایل هر بلوک را بر اساس فیلد type آن تعیین می کند.

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    TextStyle? textStyle;
    switch (block.type) {
      case 'h1':
        textStyle = Theme.of(context).textTheme.displayMedium;
      case 'p' || 'checkbox':
        textStyle = Theme.of(context).textTheme.bodyMedium;
      case _:
        textStyle = Theme.of(context).textTheme.bodySmall;
    }

    return Container(
      margin: const EdgeInsets.all(8),
      child: Text(
        block.text,
        style: textStyle,
      ),
    );
  }
}

دستور switch در متد build فیلد type شیء block را روشن می کند.

  1. عبارت case اول از یک الگوی رشته ثابت استفاده می کند. اگر block.type برابر با مقدار ثابت h1 باشد، الگو مطابقت دارد.
  2. عبارت case دوم از یک الگوی منطقی یا با دو الگوی رشته ثابت به عنوان الگوهای فرعی استفاده می کند. اگر block.type با یکی از زیرالگوهای p یا checkbox مطابقت داشته باشد، الگو مطابقت دارد.
  1. مورد نهایی یک الگوی عام است , _ . حروف عام در موارد سوئیچ با سایر موارد مطابقت دارند. آنها مانند عبارات default عمل می کنند، که هنوز در دستورات سوئیچ مجاز هستند (آنها فقط کمی پرمخاطب تر هستند).

الگوهای عام را می توان هر جا که یک الگو مجاز است استفاده کرد - به عنوان مثال، در یک الگوی اعلان متغیر: var (title, _) = document.metadata;

در این زمینه، عام هیچ متغیری را متصل نمی کند. فیلد دوم را کنار می گذارد.

در بخش بعدی، با ویژگی های سوئیچ بیشتر پس از نمایش اشیاء Block آشنا می شوید.

نمایش محتوای سند

با فراخوانی getBlocks() در متد build ویجت DocumentScreen یک متغیر محلی ایجاد کنید که حاوی لیستی از اشیاء Block باشد.

  1. متد build موجود در DocumentationScreen را با این نسخه جایگزین کنید:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;
    final blocks = document.getBlocks();                           // Add this line

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          Text('Last modified: $modified'),                        // Modify from here
          Expanded(
            child: ListView.builder(
              itemCount: blocks.length,
              itemBuilder: (context, index) {
                return BlockWidget(block: blocks[index]);
              },
            ),
          ),                                                       // to here.
        ],
      ),
    );
  }
}

خط BlockWidget(block: blocks[index]) یک ویجت BlockWidget را برای هر آیتم در لیست بلوک های برگشتی از متد getBlocks() می سازد.

  1. برنامه را اجرا کنید و سپس بلوک های ظاهر شده روی صفحه را ببینید:

تصویری از برنامه که محتوا را از بخش «بلاک‌ها» از داده‌های JSON نمایش می‌دهد.

10. از عبارات سوئیچ استفاده کنید

الگوها قابلیت های زیادی به switch و case اضافه می کنند. برای اینکه آن‌ها را در مکان‌های بیشتری قابل استفاده کند، Dart دارای عبارات سوئیچ است. مجموعه ای از موارد می توانند یک مقدار را مستقیماً به یک انتساب یا عبارت بازگشتی متغیر ارائه دهند.

عبارت switch را به عبارت switch تبدیل کنید

تحلیلگر دارت به شما کمک می کند تا در کد خود تغییراتی ایجاد کنید.

  1. مکان نما خود را به دستور switch از بخش قبل ببرید.
  2. برای مشاهده کمک های موجود روی لامپ کلیک کنید.
  3. کمک عبارت Convert to switch را انتخاب کنید.

تصویری از کمک «تبدیل به تغییر عبارت» موجود در VS Code.

نسخه جدید این کد به شکل زیر است:

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    TextStyle? textStyle;                                          // Modify from here
    textStyle = switch (block.type) {
      'h1' => Theme.of(context).textTheme.displayMedium,
      'p' || 'checkbox' => Theme.of(context).textTheme.bodyMedium,
      _ => Theme.of(context).textTheme.bodySmall
    };                                                             // to here.

    return Container(
      margin: const EdgeInsets.all(8),
      child: Text(
        block.text,
        style: textStyle,
      ),
    );
  }
}

یک عبارت switch شبیه به دستور switch به نظر می رسد، اما کلمه کلیدی case را حذف می کند و از => برای جدا کردن الگو از بدنه case استفاده می کند. برخلاف دستورهای سوئیچ، عبارات سوئیچ یک مقدار را برمی‌گردانند و می‌توانند در هر جایی که بتوان از یک عبارت استفاده کرد استفاده کرد.

11. از الگوهای شی استفاده کنید

دارت یک زبان شی گرا است، بنابراین الگوها برای همه اشیا اعمال می شوند. در این مرحله، یک الگوی شی را روشن می‌کنید و ویژگی‌های شی را تخریب می‌کنید تا منطق رندر تاریخ رابط کاربری خود را افزایش دهید.

استخراج خواص از الگوهای شی

در این بخش، نحوه نمایش آخرین تاریخ اصلاح شده را با استفاده از الگوها بهبود می دهید.

  • متد formatDate به main.dart اضافه کنید:

lib/main.dart

String formatDate(DateTime dateTime) {
  final today = DateTime.now();
  final difference = dateTime.difference(today);

  return switch (difference) {
    Duration(inDays: 0) => 'today',
    Duration(inDays: 1) => 'tomorrow',
    Duration(inDays: -1) => 'yesterday',
    Duration(inDays: final days, isNegative: true) => '${days.abs()} days ago',
    Duration(inDays: final days) => '$days days from now',
  };
}

این روش یک عبارت سوئیچ را برمی‌گرداند که difference مقدار را روشن می‌کند، یک شی Duration . این بازه زمانی بین today و مقدار modified از داده های JSON را نشان می دهد.

هر مورد از عبارت سوئیچ از یک الگوی شی استفاده می‌کند که با فراخوانی گیرنده‌های خصوصیات شی inDays و isNegative مطابقت دارد. نحو به نظر می رسد که ممکن است در حال ساخت یک شی Duration باشد، اما در واقع به فیلدهایی در شی difference دسترسی دارد.

سه حالت اول از زیرالگوهای ثابت 0 ، 1 و -1 برای مطابقت با ویژگی شی inDays و برگرداندن رشته مربوطه استفاده می کنند.

دو مورد آخر به مدت‌هایی فراتر از امروز، دیروز و فردا رسیدگی می‌کنند:

  • اگر ویژگی isNegative با الگوی ثابت بولی true مطابقت داشته باشد، به این معنی که تاریخ اصلاح در گذشته بوده است، چند روز پیش نمایش داده می شود.
  • اگر این مورد تفاوت را پیدا نکرد، مدت زمان باید تعداد روز مثبت باشد (نیازی به تأیید صریح با isNegative: false نیست)، بنابراین تاریخ اصلاح در آینده است و روزهای بعد را نشان می‌دهد.

منطق قالب بندی را برای هفته ها اضافه کنید

  • دو مورد جدید را به عملکرد قالب‌بندی خود اضافه کنید تا مدت زمان بیش از ۷ روز را شناسایی کنید تا رابط کاربری بتواند آنها را به صورت هفته‌ها نمایش دهد:

lib/main.dart

String formatDate(DateTime dateTime) {
  final today = DateTime.now();
  final difference = dateTime.difference(today);

  return switch (difference) {
    Duration(inDays: 0) => 'today',
    Duration(inDays: 1) => 'tomorrow',
    Duration(inDays: -1) => 'yesterday',
    Duration(inDays: final days) when days > 7 => '${days ~/ 7} weeks from now', // Add from here
    Duration(inDays: final days) when days < -7 =>
      '${days.abs() ~/ 7} weeks ago',                                            // to here.
    Duration(inDays: final days, isNegative: true) => '${days.abs()} days ago',
    Duration(inDays: final days) => '$days days from now',
  };
}

این کد بندهای محافظ را معرفی می کند:

  • یک بند محافظ از کلمه کلیدی when بعد از الگوی مورد استفاده می کند.
  • آنها را می توان در موارد if-case، عبارات سوئیچ و عبارات سوئیچ استفاده کرد.
  • آنها فقط یک شرط را پس از تطبیق الگو به آن اضافه می کنند.
  • اگر بند محافظ نادرست ارزیابی شود، کل الگو رد می شود و اجرا به حالت بعدی ادامه می یابد.

تاریخ تازه فرمت شده را به UI اضافه کنید

  1. در نهایت، روش build را در DocumentScreen به روز کنید تا از تابع formatDate استفاده کنید:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

  const DocumentScreen({
    required this.document,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;
    final formattedModifiedDate = formatDate(modified);            // Add this line
    final blocks = document.getBlocks();

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          Text('Last modified: $formattedModifiedDate'),           // Modify this line
          Expanded(
            child: ListView.builder(
              itemCount: blocks.length,
              itemBuilder: (context, index) {
                return BlockWidget(block: blocks[index]);
              },
            ),
          ),
        ],
      ),
    );
  }
}
  1. برای مشاهده تغییرات در برنامه خود، بارگیری مجدد داغ:

تصویری از برنامه که رشته «آخرین تغییر: ۲ هفته پیش» را با استفاده از تابع formatDate() نشان می‌دهد.

12. یک کلاس را برای تعویض کامل مهر و موم کنید

توجه داشته باشید که از علامت عام یا حروف پیش‌فرض در انتهای آخرین سوئیچ استفاده نکرده‌اید. اگرچه این تمرین خوب است که همیشه یک مورد برای مقادیری که ممکن است از بین بروند درج شود، اما در یک مثال ساده مانند این اشکالی ندارد، زیرا می‌دانید مواردی که حساب برای همه مقادیر ممکن inDays می‌توانند به‌طور بالقوه در نظر بگیرند، تعریف کرده‌اید.

هنگامی که هر مورد در یک سوئیچ رسیدگی می شود، به آن سوئیچ جامع می گویند. برای مثال، روشن کردن یک نوع bool زمانی جامع است که موارد true و false داشته باشد. روشن کردن نوع enum زمانی که مواردی برای هر یک از مقادیر enum نیز وجود داشته باشد جامع است، زیرا enum ها تعداد ثابتی از مقادیر ثابت را نشان می دهند.

دارت 3 بررسی جامعیت را به اشیا و سلسله مراتب کلاسها با اصلاح کننده کلاس جدید sealed گسترش داد. کلاس Block خود را به عنوان یک ابر کلاس مهر و موم شده بازسازی کنید.

زیر کلاس ها را ایجاد کنید

  • در data.dart ، سه کلاس جدید ایجاد کنید - HeaderBlock ، ParagraphBlock ، و CheckboxBlock - که Block گسترش می‌دهند:

lib/data.dart

class HeaderBlock extends Block {
  final String text;
  HeaderBlock(this.text);
}

class ParagraphBlock extends Block {
  final String text;
  ParagraphBlock(this.text);
}

class CheckboxBlock extends Block {
  final String text;
  final bool isChecked;
  CheckboxBlock(this.text, this.isChecked);
}

هر یک از این کلاس‌ها با مقادیر type متفاوتی از JSON اصلی مطابقت دارد: 'h1' ، 'p' و 'checkbox' .

ابر کلاس را مهر و موم کنید

  • کلاس Block را به عنوان sealed علامت گذاری کنید. سپس، if-case را به عنوان یک عبارت سوئیچ که زیرکلاس مربوط به type مشخص شده در JSON را برمی گرداند، تغییر شکل دهید:

lib/data.dart

sealed class Block {
  Block();

  factory Block.fromJson(Map<String, Object?> json) {
    return switch (json) {
      {'type': 'h1', 'text': String text} => HeaderBlock(text),
      {'type': 'p', 'text': String text} => ParagraphBlock(text),
      {'type': 'checkbox', 'text': String text, 'checked': bool checked} =>
        CheckboxBlock(text, checked),
      _ => throw const FormatException('Unexpected JSON format'),
    };
  }
}

کلمه کلیدی sealed یک اصلاح کننده کلاس است که به این معنی است که شما فقط می توانید این کلاس را در همان کتابخانه گسترش دهید یا پیاده سازی کنید . از آنجایی که تحلیلگر انواع فرعی این کلاس را می شناسد، اگر سوئیچ نتواند یکی از آنها را پوشش دهد و جامع نباشد، خطا را گزارش می کند.

برای نمایش ویجت ها از عبارت switch استفاده کنید

  1. کلاس BlockWidget را در main.dart با یک عبارت سوئیچ که از الگوهای شی برای هر مورد استفاده می کند، به روز کنید:

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(8),
      child: switch (block) {
        HeaderBlock(:final text) => Text(
            text,
            style: Theme.of(context).textTheme.displayMedium,
          ),
        ParagraphBlock(:final text) => Text(text),
        CheckboxBlock(:final text, :final isChecked) => Row(
            children: [
              Checkbox(value: isChecked, onChanged: (_) {}),
              Text(text),
            ],
          ),
      },
    );
  }
}

در اولین نسخه BlockWidget ، فیلدی از یک شی Block را برای برگرداندن یک TextStyle روشن کردید. اکنون، نمونه‌ای از خود شی Block را تغییر می‌دهید و با الگوهای شی که نشان‌دهنده زیر کلاس‌های آن هستند، مطابقت می‌دهید، و ویژگی‌های شی را در فرآیند استخراج می‌کنید.

تحلیلگر دارت می‌تواند بررسی کند که هر زیر کلاس در عبارت سوئیچ مدیریت می‌شود زیرا شما Block یک کلاس مهر و موم شده ساخته‌اید.

همچنین توجه داشته باشید که استفاده از عبارت switch در اینجا به شما امکان می دهد نتیجه را مستقیماً به عنصر child منتقل کنید، برخلاف دستور بازگشت جداگانه ای که قبلاً لازم بود.

  1. برای مشاهده چک باکس داده‌های JSON که برای اولین بار ارائه شده است، دوباره بارگیری کنید:

یک اسکرین شات از برنامه ای که چک باکس "Learn Dart 3" را نشان می دهد

13. تبریک می گویم

شما با موفقیت الگوها، رکوردها، سوئیچ و کیس پیشرفته و کلاس های مهر و موم شده را آزمایش کردید. شما اطلاعات زیادی را پوشش دادید - اما به سختی سطح این ویژگی ها را خراشیدید. برای اطلاعات بیشتر در مورد الگوها، مشخصات ویژگی را ببینید.

انواع الگوهای مختلف، زمینه‌های متفاوتی که می‌توانند در آن ظاهر شوند، و تودرتوی بالقوه زیرالگوها، احتمالات در رفتار را به ظاهر بی‌پایان می‌سازد. اما دیدن آنها آسان است.

شما می توانید انواع روش ها را برای نمایش محتوا در Flutter با استفاده از الگوها تصور کنید. با استفاده از الگوها، می توانید با خیال راحت داده ها را استخراج کنید تا رابط کاربری خود را در چند خط کد بسازید.

بعدش چی؟

  • مستندات مربوط به الگوها، رکوردها، سوئیچ ها و موارد پیشرفته و اصلاح کننده های کلاس را در بخش زبان اسناد دارت بررسی کنید.

اسناد مرجع

نمونه کد کامل را گام به گام در مخزن flutter/codelabs مشاهده کنید.

برای مشخصات دقیق هر ویژگی جدید، اسناد طراحی اصلی را بررسی کنید: