Tìm hiểu các mẫu hình và bản ghi của Dart

Tìm hiểu sâu về các mẫu và bản ghi của Dart

Thông tin về lớp học lập trình này

subjectLần cập nhật gần đây nhất: thg 4 4, 2025
account_circleTác giả: John Ryan and Marya Belanger

1. Giới thiệu

Dart 3 giới thiệu mẫu cho ngôn ngữ, một danh mục ngữ pháp mới quan trọng. Ngoài cách viết mã Dart mới này, còn có một số điểm cải tiến khác về ngôn ngữ, bao gồm

  • bản ghi để gói dữ liệu của nhiều loại,
  • đối tượng sửa đổi lớp để kiểm soát quyền truy cập và
  • Biểu thức chuyển đổicâu lệnh if-case mới.

Những tính năng này mở rộng lựa chọn cho bạn khi viết mã Dart. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng các hàm này để giúp mã của mình nhỏ gọn, đơn giản và linh hoạt hơn.

Lớp học lập trình này giả định rằng bạn đã quen thuộc với Flutter và Dart. Nếu bạn cảm thấy hơi quên, hãy xem xét việc ôn lại kiến thức cơ bản qua các tài nguyên sau:

Sản phẩm bạn sẽ tạo ra

Lớp học lập trình này tạo một ứng dụng hiển thị tài liệu JSON trong Flutter. Ứng dụng mô phỏng JSON đến từ một nguồn bên ngoài. Tệp JSON chứa dữ liệu tài liệu như ngày sửa đổi, tiêu đề, tiêu đề và đoạn văn. Bạn viết mã để đóng gói gọn gàng dữ liệu vào các bản ghi để có thể chuyển và giải nén dữ liệu bất cứ khi nào các tiện ích Flutter cần.

Sau đó, bạn sử dụng các mẫu để tạo tiện ích phù hợp khi giá trị khớp với mẫu đó. Bạn cũng sẽ thấy cách sử dụng các mẫu để huỷ cấu trúc dữ liệu thành các biến cục bộ.

Ứng dụng cuối cùng mà bạn xây dựng trong lớp học lập trình này, một tài liệu có tiêu đề, ngày sửa đổi gần đây nhất, tiêu đề và đoạn văn.

Kiến thức bạn sẽ học được

  • Cách tạo một bản ghi lưu trữ nhiều giá trị với các loại khác nhau.
  • Cách trả về nhiều giá trị từ một hàm bằng cách sử dụng bản ghi.
  • Cách sử dụng mẫu để so khớp, xác thực và huỷ cấu trúc dữ liệu từ các bản ghi và đối tượng khác.
  • Cách liên kết các giá trị khớp với mẫu với các biến mới hoặc hiện có.
  • Cách sử dụng các tính năng mới của câu lệnh switch, biểu thức switch và câu lệnh if-case.
  • Cách tận dụng tính năng kiểm tra tính toàn diện để đảm bảo mọi trường hợp đều được xử lý trong câu lệnh hoặc biểu thức chuyển đổi.

2. Thiết lập môi trường

  1. Cài đặt SDK Flutter.
  2. Thiết lập trình soạn thảo, chẳng hạn như Visual Studio Code (VS Code).
  3. Làm theo các bước Thiết lập nền tảng cho ít nhất một nền tảng mục tiêu (iOS, Android, Máy tính hoặc trình duyệt web).

3. Tạo dự án

Trước khi tìm hiểu về các mẫu, bản ghi và các tính năng mới khác, hãy dành chút thời gian để tạo một dự án Flutter đơn giản mà bạn sẽ viết tất cả mã.

Tạo dự án Flutter

  1. Sử dụng lệnh flutter create để tạo một dự án mới có tên là patterns_codelab. Cờ --empty ngăn việc tạo ứng dụng bộ đếm chuẩn trong tệp lib/main.dart. Dù sao thì bạn cũng phải xoá ứng dụng này.
flutter create --empty patterns_codelab
  1. Sau đó, mở thư mục patterns_codelab bằng VS Code.
code patterns_codelab

Ảnh chụp màn hình VS Code hiển thị dự án được tạo bằng lệnh "flutter create".

Đặt phiên bản SDK tối thiểu

  • Đặt quy tắc ràng buộc phiên bản SDK cho dự án của bạn phụ thuộc vào Dart 3 trở lên.

pubspec.yaml

environment:
  sdk: ^3.0.0

4. Thiết lập dự án

Ở bước này, bạn tạo hoặc cập nhật hai tệp Dart:

  • Tệp main.dart chứa các tiện ích cho ứng dụng và
  • Tệp data.dart cung cấp dữ liệu của ứng dụng.

Bạn sẽ tiếp tục sửa đổi cả hai tệp này trong các bước tiếp theo.

Xác định dữ liệu cho ứng dụng

  • Tạo một tệp mới lib/data.dart rồi thêm mã sau vào tệp đó:

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"
    }
  ]
}
''';

Hãy tưởng tượng một chương trình nhận dữ liệu từ một nguồn bên ngoài, chẳng hạn như luồng I/O hoặc yêu cầu HTTP. Trong lớp học lập trình này, bạn sẽ đơn giản hoá trường hợp sử dụng thực tế hơn đó bằng cách mô phỏng dữ liệu JSON đến bằng một chuỗi nhiều dòng trong biến documentJson.

Dữ liệu JSON được xác định trong lớp Document. Trong phần sau của lớp học lập trình này, bạn sẽ thêm các hàm trả về dữ liệu từ JSON đã phân tích cú pháp. Lớp này xác định và khởi tạo trường _json trong hàm khởi tạo.

Chạy ứng dụng

Lệnh flutter create tạo tệp lib/main.dart trong cấu trúc tệp Flutter mặc định.

  1. Để tạo điểm xuất phát cho ứng dụng, hãy thay thế nội dung của main.dart bằng mã sau:

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'),
         
),
       
],
     
),
   
);
 
}
}

Bạn đã thêm hai tiện ích sau vào ứng dụng:

  • DocumentApp thiết lập phiên bản mới nhất của Material Design để tạo giao diện cho giao diện người dùng.
  • DocumentScreen cung cấp bố cục trực quan của trang bằng tiện ích Scaffold.
  1. Để đảm bảo mọi thứ đang chạy suôn sẻ, hãy chạy ứng dụng trên máy chủ bằng cách nhấp vào Run and Debug (Chạy và gỡ lỗi):

Hình ảnh nút &quot;Run and debug&quot; (Chạy và gỡ lỗi) có trong phần &quot;Run and debug&quot; (Chạy và gỡ lỗi) của thanh hoạt động ở bên trái.

  1. Theo mặc định, Flutter sẽ chọn bất kỳ nền tảng mục tiêu nào có sẵn. Để thay đổi nền tảng mục tiêu, hãy chọn nền tảng hiện tại trên Thanh trạng thái:

Ảnh chụp màn hình bộ chọn nền tảng mục tiêu trong VS Code.

Bạn sẽ thấy một khung trống với các phần tử titlebody được xác định trong tiện ích DocumentScreen:

Ảnh chụp màn hình ứng dụng được tạo trong bước này.

5. Tạo và trả về bản ghi

Trong bước này, bạn sử dụng bản ghi để trả về nhiều giá trị từ một lệnh gọi hàm. Sau đó, bạn gọi hàm đó trong tiện ích DocumentScreen để truy cập vào các giá trị và phản ánh các giá trị đó trong giao diện người dùng.

Tạo và trả về một bản ghi

  • Trong data.dart, hãy thêm một phương thức getter mới vào lớp Document có tên là metadata để trả về một bản ghi:

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.
}

Kiểu dữ liệu trả về cho hàm này là một bản ghi có hai trường, một trường có kiểu String và trường còn lại có kiểu DateTime.

Câu lệnh trả về tạo một bản ghi mới bằng cách bao gồm hai giá trị trong dấu ngoặc đơn, (title, modified: now).

Trường đầu tiên là vị trí và không có tên, còn trường thứ hai có tên là modified.

Truy cập vào các trường bản ghi

  1. Trong tiện ích DocumentScreen, hãy gọi phương thức getter metadata trong phương thức build để bạn có thể lấy bản ghi và truy cập vào các giá trị của bản ghi đó:

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.
            ),
          ),
        ],
      ),
    );
  }
}

Phương thức getter metadata trả về một bản ghi được gán cho biến cục bộ metadataRecord. Bản ghi là một cách đơn giản và dễ dàng để trả về nhiều giá trị từ một lệnh gọi hàm và gán các giá trị đó cho một biến.

Để truy cập vào từng trường riêng lẻ được tạo trong bản ghi đó, bạn có thể sử dụng cú pháp getter tích hợp sẵn của bản ghi.

  • Để lấy trường vị trí (trường không có tên, chẳng hạn như title), hãy sử dụng phương thức getter $<num> trên bản ghi. Thao tác này chỉ trả về các trường chưa đặt tên.
  • Các trường được đặt tên như modified không có phương thức getter theo vị trí, vì vậy, bạn có thể sử dụng trực tiếp tên của trường, như metadataRecord.modified.

Để xác định tên của phương thức getter cho trường vị trí, hãy bắt đầu từ $1 và bỏ qua các trường được đặt tên. Ví dụ:

var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1);                               // prints y
print(record.$2);                               // prints z
  1. Tải lại nhanh để xem các giá trị JSON hiển thị trong ứng dụng. Trình bổ trợ Dart của VS Code sẽ tải lại nhanh mỗi khi bạn lưu tệp.

Ảnh chụp màn hình ứng dụng, hiển thị tiêu đề và ngày sửa đổi.

Bạn có thể thấy rằng trên thực tế, mỗi trường đều duy trì loại của trường đó.

  • Phương thức Text() lấy một Chuỗi làm đối số đầu tiên.
  • Trường modified là DateTime và được chuyển đổi thành String bằng cách sử dụng tính năng nội suy chuỗi.

Một cách an toàn khác để trả về nhiều loại dữ liệu là xác định một lớp, cách này sẽ dài dòng hơn.

6. So khớp và huỷ cấu trúc bằng mẫu

Bản ghi có thể thu thập hiệu quả nhiều loại dữ liệu và dễ dàng truyền dữ liệu đó. Bây giờ, hãy cải thiện mã của bạn bằng cách sử dụng mẫu.

Mẫu đại diện cho một cấu trúc mà một hoặc nhiều giá trị có thể nhận, giống như bản thiết kế. Mẫu so sánh với các giá trị thực tế để xác định xem các giá trị đó có khớp hay không.

Một số mẫu, khi khớp, sẽ giải cấu trúc giá trị đã khớp bằng cách lấy dữ liệu ra khỏi giá trị đó. Việc huỷ cấu trúc cho phép bạn giải nén các giá trị từ một đối tượng để gán các giá trị đó cho các biến cục bộ hoặc thực hiện việc so khớp thêm trên các giá trị đó.

Huỷ cấu trúc một bản ghi thành các biến cục bộ

  1. Tái cấu trúc phương thức build của DocumentScreen để gọi metadata và sử dụng phương thức này để khởi tạo khai báo biến mẫu:

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
            ),
          ),
        ],
      ),
    );
  }
}

Mẫu bản ghi (title, modified: modified) chứa hai mẫu biến khớp với các trường của bản ghi do metadata trả về.

  • Biểu thức này khớp với mẫu con vì kết quả là một bản ghi có hai trường, một trong số đó có tên là modified.
  • Vì chúng khớp nhau, nên mẫu khai báo biến sẽ phân tích cú pháp biểu thức, truy cập vào các giá trị của biểu thức và liên kết các giá trị đó với các biến cục bộ mới có cùng loại và tên, String titleDateTime modified.

Có một viết tắt cho trường hợp tên của trường và biến điền vào trường đó giống nhau. Tái cấu trúc phương thức build của DocumentScreen như sau.

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',
            ),
          ),
        ],
      ),
    );
  }
}

Cú pháp của mẫu biến :modified là viết tắt của modified: modified. Nếu muốn một biến cục bộ mới có tên khác, bạn có thể viết modified: localModified.

  1. Tải lại nóng để xem kết quả tương tự như trong bước trước. Hành vi vẫn giống hệt như vậy; bạn chỉ làm cho mã ngắn gọn hơn.

7. Sử dụng mẫu để trích xuất dữ liệu

Trong một số ngữ cảnh nhất định, mẫu không chỉ so khớp và huỷ cấu trúc mà còn có thể đưa ra quyết định về việc mã làm gì, dựa trên việc mẫu có khớp hay không. Đây được gọi là mẫu có thể bác bỏ.

Mẫu khai báo biến mà bạn sử dụng ở bước cuối cùng là một mẫu không thể phủ nhận: giá trị phải khớp với mẫu, nếu không sẽ xảy ra lỗi và quá trình huỷ cấu trúc sẽ không diễn ra. Hãy nghĩ đến bất kỳ nội dung khai báo hoặc gán biến nào; bạn không thể gán giá trị cho một biến nếu chúng không cùng kiểu.

Mặt khác, các mẫu có thể bác bỏ được dùng trong ngữ cảnh luồng điều khiển:

  • Họ dự kiến rằng một số giá trị mà họ so sánh sẽ không khớp.
  • Các giá trị này nhằm ảnh hưởng đến luồng điều khiển, dựa trên việc giá trị có khớp hay không.
  • Các câu lệnh này không làm gián đoạn quá trình thực thi bằng lỗi nếu không khớp, mà chỉ chuyển sang câu lệnh tiếp theo.
  • Các hàm này có thể huỷ cấu trúc và liên kết các biến chỉ có thể sử dụng khi khớp

Đọc giá trị JSON không có mẫu

Trong phần này, bạn sẽ đọc dữ liệu mà không cần so khớp mẫu để xem các mẫu có thể giúp bạn làm việc với dữ liệu JSON như thế nào.

  • Thay thế phiên bản metadata trước đó bằng phiên bản đọc các giá trị từ bản đồ _json. Sao chép và dán phiên bản metadata này vào lớp 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.
  }
}

Mã này xác thực rằng dữ liệu được cấu trúc chính xác mà không cần sử dụng mẫu. Trong một bước sau, bạn sẽ sử dụng tính năng so khớp mẫu để thực hiện quy trình xác thực tương tự bằng cách sử dụng ít mã hơn. Phương thức này thực hiện 3 bước kiểm tra trước khi làm bất cứ việc gì khác:

  • Tệp JSON chứa cấu trúc dữ liệu mà bạn mong đợi: if (_json.containsKey('metadata'))
  • Dữ liệu có loại mà bạn mong đợi: if (metadataJson is Map)
  • Dữ liệu không rỗng, được xác nhận ngầm trong lần kiểm tra trước.

Đọc giá trị JSON bằng mẫu bản đồ

Với mẫu có thể bác bỏ, bạn có thể xác minh rằng JSON có cấu trúc dự kiến bằng cách sử dụng mẫu ánh xạ.

  • Thay thế phiên bản metadata trước đó bằng mã sau:

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.
  }
}

Tại đây, bạn sẽ thấy một loại câu lệnh if-statement mới (được giới thiệu trong Dart 3), if-case. Phần nội dung của trường hợp chỉ thực thi nếu mẫu trường hợp khớp với dữ liệu trong _json. Việc so khớp này thực hiện các bước kiểm tra tương tự như bạn đã viết trong phiên bản đầu tiên của metadata để xác thực JSON đến. Mã này xác thực những nội dung sau:

  • _json là một loại Bản đồ.
  • _json chứa khoá metadata.
  • _json không phải là giá trị rỗng.
  • _json['metadata'] cũng là một loại Bản đồ.
  • _json['metadata'] chứa các khoá titlemodified.
  • titlelocalModified là các chuỗi và không phải là giá trị rỗng.

Nếu giá trị không khớp, mẫu sẽ bác bỏ (từ chối tiếp tục thực thi) và chuyển sang mệnh đề else. Nếu khớp thành công, mẫu sẽ huỷ cấu trúc các giá trị của titlemodified từ bản đồ và liên kết các giá trị đó với các biến cục bộ mới.

Để xem danh sách đầy đủ các mẫu, hãy xem bảng trong phần Mẫu của quy cách tính năng.

8. Chuẩn bị ứng dụng cho nhiều mẫu hơn

Cho đến nay, bạn đã xử lý phần metadata của dữ liệu JSON. Trong bước này, bạn sẽ tinh chỉnh thêm một chút logic nghiệp vụ để xử lý dữ liệu trong danh sách blocks và hiển thị dữ liệu đó trong ứng dụng.

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

Tạo một lớp lưu trữ dữ liệu

  • Thêm một lớp mới, Block, vào data.dart. Lớp này dùng để đọc và lưu trữ dữ liệu cho một trong các khối trong dữ liệu 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');
    }
  }
}

Hàm khởi tạo nhà máy fromJson() sử dụng cùng một trường hợp if với mẫu bản đồ mà bạn đã thấy trước đây.

Bạn sẽ thấy dữ liệu JSON trông giống như mẫu dự kiến, mặc dù dữ liệu này có thêm một phần thông tin có tên là checked không có trong mẫu. Lý do là khi bạn sử dụng những loại mẫu này (được gọi là "mẫu bản đồ"), chúng chỉ quan tâm đến những điều cụ thể mà bạn đã xác định trong mẫu và bỏ qua mọi thứ khác trong dữ liệu.

Trả về danh sách đối tượng Khối

  • Tiếp theo, hãy thêm một hàm mới, getBlocks(), vào lớp Document. getBlocks() phân tích cú pháp JSON thành các thực thể của lớp Block và trả về danh sách các khối để hiển thị trong giao diện người dùng:

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.
}

Hàm getBlocks() trả về danh sách các đối tượng Block mà bạn sẽ sử dụng sau để tạo giao diện người dùng. Một câu lệnh if-case quen thuộc sẽ thực hiện việc xác thực và truyền giá trị của siêu dữ liệu blocks vào một List mới có tên là blocksJson (không có mẫu, bạn cần phương thức toList() để truyền).

Danh sách cố định chứa một bộ sưu tập cho để điền các đối tượng Block vào danh sách mới.

Phần này không giới thiệu bất kỳ tính năng nào liên quan đến mẫu mà bạn chưa thử trong lớp học lập trình này. Trong bước tiếp theo, bạn sẽ chuẩn bị hiển thị các mục trong danh sách trong giao diện người dùng.

9. Sử dụng mẫu để hiển thị tài liệu

Giờ đây, bạn đã huỷ cấu trúc và kết hợp lại dữ liệu JSON thành công bằng cách sử dụng câu lệnh if-case và các mẫu có thể bác bỏ. Tuy nhiên, câu lệnh if-case chỉ là một trong những tính năng nâng cao để kiểm soát cấu trúc luồng đi kèm với các mẫu. Bây giờ, bạn sẽ áp dụng kiến thức về các mẫu có thể bác bỏ để chuyển đổi câu lệnh.

Kiểm soát nội dung hiển thị bằng các mẫu có câu lệnh chuyển

  • Trong main.dart, hãy tạo một tiện ích mới, BlockWidget, để xác định kiểu của mỗi khối dựa trên trường type của tiện ích đó.

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,
      ),
    );
  }
}

Câu lệnh chuyển đổi trong phương thức build sẽ bật trường type của đối tượng block.

  1. Câu lệnh case đầu tiên sử dụng mẫu chuỗi hằng số. Mẫu sẽ khớp nếu block.type bằng giá trị hằng số h1.
  2. Câu lệnh trường hợp thứ hai sử dụng mẫu logic-hoặc với hai mẫu chuỗi không đổi làm mẫu con. Mẫu sẽ khớp nếu block.type khớp với một trong hai mẫu con p hoặc checkbox.
  1. Trường hợp cuối cùng là mẫu ký tự đại diện, _. Ký tự đại diện trong các trường hợp chuyển đổi khớp với mọi nội dung khác. Các mệnh đề này hoạt động giống như mệnh đề default, vẫn được phép sử dụng trong câu lệnh switch (chỉ dài hơn một chút).

Bạn có thể sử dụng mẫu ký tự đại diện ở bất cứ nơi nào cho phép mẫu, ví dụ: trong mẫu khai báo biến: var (title, _) = document.metadata;

Trong ngữ cảnh này, ký tự đại diện không liên kết với bất kỳ biến nào. Hàm này sẽ loại bỏ trường thứ hai.

Trong phần tiếp theo, bạn sẽ tìm hiểu thêm về các tính năng của nút chuyển sau khi hiển thị các đối tượng Block.

Hiển thị nội dung tài liệu

Tạo một biến cục bộ chứa danh sách đối tượng Block bằng cách gọi getBlocks() trong phương thức build của tiện ích DocumentScreen.

  1. Thay thế phương thức build hiện có trong DocumentationScreen bằng phiên bản này:

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.
        ],
      ),
    );
  }
}

Dòng BlockWidget(block: blocks[index]) tạo một tiện ích BlockWidget cho mỗi mục trong danh sách các khối được trả về từ phương thức getBlocks().

  1. Chạy ứng dụng, sau đó bạn sẽ thấy các khối xuất hiện trên màn hình:

Ảnh chụp màn hình ứng dụng hiển thị nội dung từ phần &quot;khối&quot; của dữ liệu JSON.

10. Sử dụng biểu thức chuyển

Mẫu bổ sung nhiều chức năng cho switchcase. Để có thể sử dụng các biểu thức này ở nhiều nơi hơn, Dart có biểu thức chuyển đổi. Một loạt các trường hợp có thể cung cấp giá trị trực tiếp cho câu lệnh gán biến hoặc câu lệnh trả về.

Chuyển đổi câu lệnh switch thành biểu thức switch

Trình phân tích Dart cung cấp hỗ trợ để giúp bạn thực hiện các thay đổi đối với mã.

  1. Di chuyển con trỏ đến câu lệnh switch từ phần trước.
  2. Nhấp vào biểu tượng bóng đèn để xem các tính năng hỗ trợ có sẵn.
  3. Chọn tính năng hỗ trợ Chuyển đổi thành biểu thức chuyển đổi.

Ảnh chụp màn hình về tính năng hỗ trợ &quot;chuyển đổi thành biểu thức chuyển đổi&quot; có trong VS Code.

Phiên bản mới của mã này sẽ có dạng như sau:

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,
      ),
    );
  }
}

Biểu thức switch trông giống như câu lệnh switch, nhưng loại bỏ từ khoá case và sử dụng => để tách mẫu khỏi phần nội dung case. Không giống như câu lệnh switch, biểu thức switch trả về một giá trị và có thể được sử dụng ở bất cứ nơi nào có thể sử dụng biểu thức.

11. Sử dụng mẫu đối tượng

Dart là một ngôn ngữ hướng đối tượng, vì vậy, các mẫu áp dụng cho tất cả đối tượng. Trong bước này, bạn sẽ bật một mẫu đối tượng và huỷ cấu trúc các thuộc tính đối tượng để cải thiện logic hiển thị ngày của giao diện người dùng.

Trích xuất thuộc tính từ mẫu đối tượng

Trong phần này, bạn cải thiện cách hiển thị ngày sửa đổi gần nhất bằng cách sử dụng mẫu.

  • Thêm phương thức formatDate vào 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',
  };
}

Phương thức này trả về một biểu thức chuyển đổi bật giá trị difference, một đối tượng Duration. Giá trị này biểu thị khoảng thời gian giữa today và giá trị modified từ dữ liệu JSON.

Mỗi trường hợp của biểu thức switch đang sử dụng một mẫu đối tượng khớp bằng cách gọi phương thức getter trên các thuộc tính của đối tượng inDaysisNegative. Cú pháp này có vẻ như đang tạo một đối tượng Duration, nhưng thực tế là đang truy cập vào các trường trên đối tượng difference.

Ba trường hợp đầu tiên sử dụng mẫu con không đổi 0, 1-1 để so khớp thuộc tính đối tượng inDays và trả về chuỗi tương ứng.

Hai trường hợp cuối cùng xử lý các khoảng thời gian ngoài hôm nay, hôm qua và ngày mai:

  • Nếu thuộc tính isNegative khớp với mẫu hằng số booleantrue, nghĩa là ngày sửa đổi đã qua, thì thuộc tính này sẽ hiển thị ngày trước.
  • Nếu trường hợp đó không phát hiện được sự khác biệt, thì thời lượng phải là một số ngày dương (không cần xác minh rõ ràng bằng isNegative: false), vì vậy, ngày sửa đổi sẽ là trong tương lai và hiển thị ngày kể từ bây giờ.

Thêm logic định dạng cho tuần

  • Thêm hai trường hợp mới vào hàm định dạng để xác định các khoảng thời gian dài hơn 7 ngày để giao diện người dùng có thể hiển thị dưới dạng tuần:

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',
  };
}

Mã này giới thiệu câu lệnh bảo vệ:

  • Mệnh đề bảo vệ sử dụng từ khoá when sau mẫu trường hợp.
  • Bạn có thể sử dụng các giá trị này trong trường hợp if, câu lệnh switch và biểu thức switch.
  • Hàm này chỉ thêm điều kiện vào mẫu sau khi mẫu đó được so khớp.
  • Nếu mệnh đề bảo vệ đánh giá là sai, toàn bộ mẫu sẽ bị phản bác và quá trình thực thi sẽ chuyển sang trường hợp tiếp theo.

Thêm ngày được định dạng mới vào giao diện người dùng

  1. Cuối cùng, hãy cập nhật phương thức build trong DocumentScreen để sử dụng hàm 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. Tải lại nhanh để xem các thay đổi trong ứng dụng:

Ảnh chụp màn hình ứng dụng hiển thị chuỗi &quot;Lần sửa đổi gần đây nhất: 2 tuần trước&quot; bằng hàm formatDate().

12. Niêm phong một lớp để chuyển đổi toàn diện

Lưu ý rằng bạn không sử dụng ký tự đại diện hoặc trường hợp mặc định ở cuối nút chuyển cuối cùng. Mặc dù bạn nên luôn thêm một trường hợp cho các giá trị có thể bị bỏ qua, nhưng trong ví dụ đơn giản như thế này thì không sao vì bạn biết các trường hợp bạn đã xác định tính đến tất cả các giá trị có thể có inDays có thể nhận được.

Khi mọi trường hợp trong một nút chuyển được xử lý, nút chuyển đó được gọi là nút chuyển toàn diện. Ví dụ: việc bật loại bool sẽ đầy đủ khi có các trường hợp cho truefalse. Việc bật loại enum cũng sẽ đầy đủ khi có các trường hợp cho từng giá trị của enum, vì enum đại diện cho một số cố định của giá trị không đổi.

Dart 3 đã mở rộng tính năng kiểm tra toàn diện cho các đối tượng và hệ phân cấp lớp bằng đối tượng sửa đổi lớp mới sealed. Tái cấu trúc lớp Block dưới dạng lớp mẹ kín.

Tạo lớp con

  • Trong data.dart, hãy tạo ba lớp mới – HeaderBlock, ParagraphBlockCheckboxBlock – mở rộng 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);
}

Mỗi lớp trong số này tương ứng với các giá trị type khác nhau trong JSON ban đầu: 'h1', 'p''checkbox'.

Niêm phong lớp cao cấp

  • Đánh dấu lớp Blocksealed. Sau đó, hãy tái cấu trúc trường hợp if dưới dạng biểu thức chuyển đổi trả về lớp con tương ứng với type được chỉ định trong 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'),
    };
  }
}

Từ khoá sealed là một công cụ sửa đổi lớp, nghĩa là bạn chỉ có thể mở rộng hoặc triển khai lớp này trong cùng một thư viện. Vì trình phân tích biết các loại phụ của lớp này, nên trình phân tích sẽ báo cáo lỗi nếu một nút chuyển không bao gồm một trong các loại phụ đó và không đầy đủ.

Sử dụng biểu thức chuyển đổi để hiển thị tiện ích

  1. Cập nhật lớp BlockWidget trong main.dart bằng một biểu thức chuyển đổi sử dụng mẫu đối tượng cho từng trường hợp:

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),
            ],
          ),
      },
    );
  }
}

Trong phiên bản đầu tiên của BlockWidget, bạn đã bật một trường của đối tượng Block để trả về TextStyle. Bây giờ, bạn sẽ chuyển đổi một thực thể của chính đối tượng Block và so khớp với mẫu đối tượng đại diện cho các lớp con của đối tượng đó, đồng thời trích xuất các thuộc tính của đối tượng trong quá trình này.

Trình phân tích Dart có thể kiểm tra để đảm bảo rằng mỗi lớp con được xử lý trong biểu thức chuyển đổi vì bạn đã đặt Block làm lớp kín.

Ngoài ra, hãy lưu ý rằng việc sử dụng biểu thức chuyển đổi ở đây cho phép bạn truyền trực tiếp kết quả đến phần tử child, thay vì câu lệnh trả về riêng biệt cần thiết trước đó.

  1. Tải lại nhanh để xem dữ liệu JSON của hộp đánh dấu được hiển thị lần đầu tiên:

Ảnh chụp màn hình ứng dụng hiển thị hộp đánh dấu &quot;Tìm hiểu Dart 3&quot;

13. Xin chúc mừng

Bạn đã thử nghiệm thành công các mẫu, bản ghi, nút chuyển và trường hợp nâng cao cũng như các lớp kín. Bạn đã đề cập đến nhiều thông tin nhưng chỉ mới tìm hiểu sơ qua về các tính năng này. Để biết thêm thông tin về mẫu, hãy xem thông số kỹ thuật của tính năng.

Các loại mẫu khác nhau, các ngữ cảnh khác nhau mà chúng có thể xuất hiện và khả năng lồng ghép các mẫu con khiến cho các khả năng trong hành vi dường như vô tận. Nhưng bạn có thể dễ dàng nhìn thấy chúng.

Bạn có thể tưởng tượng mọi cách hiển thị nội dung trong Flutter bằng các mẫu. Khi sử dụng các mẫu, bạn có thể trích xuất dữ liệu một cách an toàn để xây dựng giao diện người dùng trong vài dòng mã.

Tiếp theo là gì?

  • Hãy xem tài liệu về mẫu, bản ghi, nút chuyển và trường hợp nâng cao cũng như đối tượng sửa đổi lớp trong Mục ngôn ngữ của tài liệu Dart.

Tài liệu tham khảo

Xem mã mẫu đầy đủ, từng bước, trong kho lưu trữ flutter/codelabs.

Để biết thông số kỹ thuật chuyên sâu về từng tính năng mới, hãy xem tài liệu thiết kế ban đầu: