1. บทนำ
Dart 3 ได้เพิ่มรูปแบบลงในภาษา ซึ่งเป็นหมวดไวยากรณ์ใหม่ที่สำคัญ นอกเหนือจากวิธีใหม่ในการเขียนโค้ด Dart แล้ว ยังมีการปรับปรุงภาษาอื่นๆ อีกหลายอย่าง ได้แก่
- ระเบียนสำหรับการจัดกลุ่มข้อมูลประเภทต่างๆ
- ตัวแก้ไขคลาสสำหรับการควบคุมการเข้าถึง และ
- นิพจน์ switch และคำสั่ง if-case ใหม่
ฟีเจอร์เหล่านี้จะขยายตัวเลือกที่คุณมีเมื่อเขียนโค้ด Dart ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีใช้เพื่อทำให้โค้ดกระชับ คล่องตัว และยืดหยุ่นมากขึ้น
Codelab นี้มีสมมติฐานว่าคุณคุ้นเคยกับ Flutter และ Dart อยู่บ้าง หากคุณรู้สึกว่ายังไม่คล่องแคล่ว ลองทบทวนพื้นฐานด้วยแหล่งข้อมูลต่อไปนี้
สิ่งที่คุณจะสร้าง
Codelab นี้สร้างแอปพลิเคชันที่แสดงเอกสาร JSON ใน Flutter แอปพลิเคชันจะจำลอง JSON ที่มาจากแหล่งข้อมูลภายนอก ไฟล์ JSON ประกอบด้วยข้อมูลเอกสาร เช่น วันที่แก้ไข ชื่อ ส่วนหัว และย่อหน้า คุณเขียนโค้ดเพื่อจัดแพ็กเกจข้อมูลลงในระเบียนอย่างเรียบร้อยเพื่อให้สามารถโอนและแกะแพ็กเกจได้ทุกที่ที่วิดเจ็ต Flutter ต้องการ
จากนั้นใช้รูปแบบเพื่อสร้างวิดเจ็ตที่เหมาะสมเมื่อค่าตรงกับรูปแบบนั้น นอกจากนี้ คุณยังจะได้ดูวิธีใช้รูปแบบเพื่อแยกโครงสร้างข้อมูลเป็นตัวแปรภายใน

สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างระเบียนที่จัดเก็บค่าหลายค่าที่มีประเภทต่างกัน
- วิธีแสดงค่าหลายค่าจากฟังก์ชันโดยใช้ระเบียน
- วิธีใช้รูปแบบเพื่อจับคู่ ตรวจสอบ และแยกโครงสร้างข้อมูลจากระเบียนและออบเจ็กต์อื่นๆ
- วิธีเชื่อมโยงค่าที่ตรงกับรูปแบบกับตัวแปรใหม่หรือตัวแปรที่มีอยู่
- วิธีใช้ความสามารถของคำสั่ง Switch, นิพจน์ Switch และคำสั่ง if-case ใหม่
- วิธีใช้ประโยชน์จากการตรวจสอบความครอบคลุมเพื่อให้มั่นใจว่าทุกกรณีจะได้รับการจัดการในคำสั่ง switch หรือนิพจน์ switch
2. ตั้งค่าสภาพแวดล้อม
- ติดตั้ง Flutter SDK
- ตั้งค่าโปรแกรมแก้ไข เช่น Visual Studio Code (VS Code)
- ทําตามขั้นตอนการตั้งค่าแพลตฟอร์มสําหรับแพลตฟอร์มเป้าหมายอย่างน้อย 1 แพลตฟอร์ม (iOS, Android, เดสก์ท็อป หรือเว็บเบราว์เซอร์)
3. สร้างโปรเจ็กต์
ก่อนที่จะเจาะลึกรูปแบบ บันทึก และฟีเจอร์ใหม่อื่นๆ ลองใช้เวลาสักครู่เพื่อสร้างโปรเจ็กต์ Flutter ที่คุณเขียนโค้ดทั้งหมด
สร้างโปรเจ็กต์ Flutter
- ใช้คำสั่ง
flutter createเพื่อสร้างโปรเจ็กต์ใหม่ชื่อpatterns_codelabFlag--emptyจะป้องกันการสร้างแอปตัวนับมาตรฐานในไฟล์lib/main.dartซึ่งคุณจะต้องนำออกอยู่ดี
flutter create --empty patterns_codelab
- จากนั้นเปิดไดเรกทอรี
patterns_codelabโดยใช้ VS Code
code patterns_codelab

ตั้งค่า SDK เวอร์ชันขั้นต่ำ
- ตั้งค่าข้อจำกัดเวอร์ชัน SDK สำหรับโปรเจ็กต์ให้ขึ้นอยู่กับ Dart 3 ขึ้นไป
pubspec.yaml
environment:
sdk: ^3.0.0
4. ตั้งค่าโปรเจ็กต์
ในขั้นตอนนี้ คุณจะได้สร้างหรืออัปเดตไฟล์ Dart 2 ไฟล์ ดังนี้
main.dartไฟล์ที่มีวิดเจ็ตสำหรับแอป และ- ไฟล์
data.dartที่ให้ข้อมูลของแอป
คุณจะแก้ไขไฟล์ทั้ง 2 นี้ต่อไปในขั้นตอนถัดไป
กำหนดข้อมูลสำหรับแอป
- สร้างไฟล์ใหม่
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"
}
]
}
''';
ลองนึกถึงโปรแกรมที่รับข้อมูลจากแหล่งที่มาภายนอก เช่น สตรีม I/O หรือคำขอ HTTP ในโค้ดแล็บนี้ คุณจะลดความซับซ้อนของกรณีการใช้งานที่สมจริงยิ่งขึ้นโดยจำลองข้อมูล JSON ที่เข้ามาด้วยสตริงหลายบรรทัดในตัวแปร documentJson
ข้อมูล JSON จะกำหนดไว้ในคลาส Document ในโค้ดแล็บนี้ คุณจะได้เพิ่มฟังก์ชันที่แสดงข้อมูลจาก JSON ที่แยกวิเคราะห์แล้ว คลาสนี้จะกำหนดและเริ่มต้นฟิลด์ _json ในตัวสร้าง
เรียกใช้แอป
คำสั่ง flutter create จะสร้างไฟล์ lib/main.dart เป็นส่วนหนึ่งของโครงสร้างไฟล์ Flutter เริ่มต้น
- หากต้องการสร้างจุดเริ่มต้นของแอปพลิเคชัน ให้แทนที่เนื้อหาของ
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(),
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'))]),
);
}
}
คุณได้เพิ่มวิดเจ็ต 2 รายการต่อไปนี้ลงในแอป
DocumentAppจะตั้งค่า Material Design เวอร์ชันล่าสุดสำหรับการกำหนดธีม UIDocumentScreenจะแสดงเลย์เอาต์ภาพของหน้าเว็บโดยใช้วิดเจ็ตScaffold
- หากต้องการให้ทุกอย่างทำงานได้อย่างราบรื่น ให้เรียกใช้แอปในเครื่องโฮสต์โดยคลิกเรียกใช้และแก้ไขข้อบกพร่อง

- โดยค่าเริ่มต้น Flutter จะเลือกแพลตฟอร์มเป้าหมายที่พร้อมใช้งาน หากต้องการเปลี่ยนแพลตฟอร์มเป้าหมาย ให้เลือกแพลตฟอร์มปัจจุบันในแถบสถานะ

คุณควรเห็นเฟรมว่างที่มีองค์ประกอบ title และ body ที่กำหนดไว้ในวิดเจ็ต DocumentScreen ดังนี้

5. สร้างและส่งคืนระเบียน
ในขั้นตอนนี้ คุณจะใช้ระเบียนเพื่อแสดงค่าหลายค่าจากการเรียกฟังก์ชัน จากนั้นคุณจะเรียกใช้ฟังก์ชันนั้นในวิดเจ็ต DocumentScreen เพื่อเข้าถึงค่าและแสดงค่าเหล่านั้นใน UI
สร้างและส่งคืนระเบียน
- ใน
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.
}
ประเภทการแสดงผลของฟังก์ชันนี้คือระเบียนที่มี 2 ฟิลด์ โดยฟิลด์หนึ่งมีประเภท String และอีกฟิลด์หนึ่งมีประเภท DateTime
คำสั่ง return จะสร้างระเบียนใหม่โดยการใส่ค่าทั้ง 2 ไว้ในวงเล็บ (title, modified: now)
ฟิลด์แรกเป็นฟิลด์ตามตำแหน่งและไม่มีชื่อ ส่วนฟิลด์ที่ 2 มีชื่อว่า modified
เข้าถึงฟิลด์ของบันทึก
- ในวิดเจ็ต
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: [ // And the following line.
Center(child: Text('Last modified ${metadataRecord.modified}')),
],
),
);
}
}
metadataเมธอด Getter จะแสดงผลระเบียนซึ่งกำหนดให้กับตัวแปรในเครื่อง metadataRecord ระเบียนเป็นวิธีที่ง่ายและมีประสิทธิภาพในการแสดงค่าหลายค่าจากการเรียกฟังก์ชันเดียวและกำหนดค่าเหล่านั้นให้กับตัวแปร
หากต้องการเข้าถึงฟิลด์แต่ละรายการที่ประกอบขึ้นในระเบียนนั้น คุณสามารถใช้ไวยากรณ์ Getter ในตัวของระเบียนได้
- หากต้องการรับฟิลด์ตามตำแหน่ง (ฟิลด์ที่ไม่มีชื่อ เช่น
title) ให้ใช้ Getterในระเบียน ซึ่งจะแสดงเฉพาะฟิลด์ที่ไม่มีชื่อ - ฟิลด์ที่มีชื่อ เช่น
modifiedจะไม่มีตัวรับตำแหน่ง คุณจึงใช้ชื่อของฟิลด์ได้โดยตรง เช่นmetadataRecord.modified
หากต้องการกำหนดชื่อของ Getter สำหรับฟิลด์ตำแหน่ง ให้เริ่มที่ $1 และข้ามฟิลด์ที่มีชื่อ เช่น
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- โหลดซ้ำด่วนเพื่อดูค่า JSON ที่แสดงในแอป ปลั๊กอิน Dart ของ VS Code จะโหลดซ้ำด่วนทุกครั้งที่คุณบันทึกไฟล์

คุณจะเห็นว่าแต่ละฟิลด์ยังคงมีประเภทของตัวเอง
- เมธอด
Text()จะใช้สตริงเป็นอาร์กิวเมนต์แรก - ฟิลด์
modifiedเป็น DateTime และจะแปลงเป็นStringโดยใช้การแทรกสตริง
อีกวิธีที่ปลอดภัยในการคืนค่าข้อมูลประเภทต่างๆ คือการกำหนดคลาส ซึ่งจะมีความละเอียดมากกว่า
6. จับคู่และแยกโครงสร้างด้วยรูปแบบ
Record สามารถรวบรวมข้อมูลประเภทต่างๆ ได้อย่างมีประสิทธิภาพและส่งต่อได้อย่างง่ายดาย ตอนนี้มาปรับปรุงโค้ดโดยใช้รูปแบบกัน
รูปแบบแสดงถึงโครงสร้างที่ค่าอย่างน้อย 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 from here...
body: Column(children: [Center(child: Text('Last modified $modified'))]),
); // To here.
}
}
รูปแบบระเบียน (title, modified: modified) มีรูปแบบตัวแปร 2 รูปแบบที่ตรงกับฟิลด์ของระเบียนที่ metadata แสดงผล
- นิพจน์จะตรงกับรูปแบบย่อยเนื่องจากผลลัพธ์เป็นระเบียนที่มี 2 ฟิลด์ ซึ่งฟิลด์หนึ่งมีชื่อว่า
modified - เนื่องจากตรงกัน รูปแบบการประกาศตัวแปรจึงแยกโครงสร้างของนิพจน์ เข้าถึงค่า และเชื่อมโยงค่าเหล่านั้นกับตัวแปรภายในใหม่ที่มีประเภทและชื่อเดียวกัน
String titleและDateTime modified
มีรูปแบบย่อสำหรับกรณีที่ชื่อฟิลด์และตัวแปรที่ป้อนข้อมูลในฟิลด์นั้นเหมือนกัน ปรับโครงสร้างเมธอด build ของ DocumentScreen ดังนี้
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 แทน
- โหลดซ้ำด่วนเพื่อดูผลลัพธ์เดียวกันกับในขั้นตอนก่อนหน้า ลักษณะการทำงานจะเหมือนกันทุกประการ เพียงแต่คุณทำให้โค้ดกระชับขึ้น
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.
}
}
โค้ดนี้จะตรวจสอบว่าข้อมูลมีโครงสร้างถูกต้องโดยไม่ต้องใช้รูปแบบ ในขั้นตอนถัดไป คุณจะใช้การจับคู่รูปแบบเพื่อทำการตรวจสอบเดียวกันโดยใช้โค้ดน้อยลง โดยจะตรวจสอบ 3 อย่างก่อนดำเนินการอื่นๆ ดังนี้
- 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 case { // Modify from here...
'metadata': {'title': String title, 'modified': String localModified},
}) {
return (title, modified: DateTime.parse(localModified));
} else {
throw const FormatException('Unexpected JSON');
} // to here.
}
}
ในที่นี้ คุณจะเห็นคำสั่ง if รูปแบบใหม่ (เปิดตัวใน Dart 3) ซึ่งก็คือ if-case เนื้อหาของเคสจะทํางานก็ต่อเมื่อรูปแบบเคสตรงกับข้อมูลใน _json การจับคู่นี้จะทำการตรวจสอบเดียวกันกับที่คุณเขียนในmetadataเวอร์ชันแรกเพื่อตรวจสอบ JSON ที่เข้ามา โค้ดนี้จะตรวจสอบสิ่งต่อไปนี้
_jsonเป็นประเภทแผนที่_jsonมีคีย์metadata_jsonไม่เป็นค่าว่าง_json['metadata']ยังเป็นประเภทแผนที่ด้วย_json['metadata']มีคีย์titleและmodifiedtitleและlocalModifiedเป็นสตริงและไม่ใช่ค่า Null
หากค่าไม่ตรงกัน รูปแบบจะปฏิเสธ (ปฏิเสธที่จะดำเนินการต่อ) และไปยังข้อความ 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()ลงในคลาสDocumentgetBlocks()จะแยกวิเคราะห์ JSON เป็นอินสแตนซ์ของคลาสBlockและแสดงผลรายการบล็อกเพื่อแสดงใน UI
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 ซึ่งคุณจะใช้ในภายหลังเพื่อสร้าง UI คำสั่ง if-case ที่คุ้นเคยจะทำการตรวจสอบและส่งค่าของblocks metadata ไปยัง List ใหม่ที่ชื่อ blocksJson (หากไม่มีรูปแบบ คุณจะต้องใช้วิธี toList() ในการส่ง)
ตัวอักษรรายการมี collection for เพื่อเติมออบเจ็กต์ Block ลงในรายการใหม่
ส่วนนี้ไม่ได้แนะนำฟีเจอร์ที่เกี่ยวข้องกับรูปแบบที่คุณยังไม่ได้ลองใช้ในโค้ดแล็บนี้ ในขั้นตอนถัดไป คุณจะเตรียมแสดงรายการใน UI
9. ใช้รูปแบบเพื่อแสดงเอกสาร
ตอนนี้คุณแยกโครงสร้างและประกอบข้อมูล JSON ใหม่ได้สำเร็จแล้ว โดยใช้คำสั่ง if-case และรูปแบบที่พิสูจน์ได้ แต่ if-case เป็นเพียงหนึ่งในการเพิ่มประสิทธิภาพเพื่อควบคุมโครงสร้างโฟลว์ที่มาพร้อมกับรูปแบบ ตอนนี้คุณจะใช้ความรู้เกี่ยวกับรูปแบบที่หักล้างได้กับคำสั่ง switch
ควบคุมสิ่งที่แสดงผลโดยใช้รูปแบบด้วยคำสั่ง switch
- ใน
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
- คำสั่ง Case แรกใช้รูปแบบสตริงค่าคงที่ รูปแบบจะตรงกันหาก
block.typeเท่ากับค่าคงที่h1 - คำสั่ง CASE ที่ 2 ใช้รูปแบบตรรกะหรือที่มีรูปแบบสตริงคงที่ 2 รูปแบบเป็นรูปแบบย่อย รูปแบบจะตรงกันหาก
block.typeตรงกับรูปแบบย่อยpหรือcheckbox
- กรณีสุดท้ายคือรูปแบบไวลด์การ์ด
_ไวลด์การ์ดในเคสของคำสั่ง switch จะตรงกับทุกอย่าง โดยจะทำงานเหมือนกับdefaultclauses ซึ่งยังคงใช้ได้ในคำสั่ง switch (เพียงแต่จะยาวกว่าเล็กน้อย)
คุณใช้รูปแบบไวลด์การ์ดได้ทุกที่ที่อนุญาตให้ใช้รูปแบบ เช่น ในรูปแบบการประกาศตัวแปร var (title, _) = document.metadata;
ในบริบทนี้ ไวลด์การ์ดจะไม่ผูกตัวแปรใดๆ โดยจะทิ้งฟิลด์ที่ 2
ในส่วนถัดไป คุณจะได้เรียนรู้เกี่ยวกับฟีเจอร์สวิตช์เพิ่มเติมหลังจากแสดงออบเจ็กต์ Block
แสดงเนื้อหาของเอกสาร
สร้างตัวแปรในเครื่องที่มีรายการออบเจ็กต์ Block โดยเรียกใช้ getBlocks() ในเมธอด build ของวิดเจ็ต DocumentScreen
- แทนที่เมธอด
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()
- เรียกใช้แอปพลิเคชัน แล้วคุณจะเห็นบล็อกปรากฏบนหน้าจอ

10. ใช้นิพจน์ Switch
รูปแบบช่วยเพิ่มความสามารถมากมายให้กับ switch และ case Dart มีนิพจน์ switch เพื่อให้ใช้ได้ในที่อื่นๆ ชุดเคสสามารถระบุค่าให้กับคำสั่งการกำหนดตัวแปรหรือคำสั่ง return ได้โดยตรง
แปลงคำสั่ง switch เป็นนิพจน์ switch
โปรแกรมวิเคราะห์ Dart มีความช่วยเหลือที่จะช่วยคุณทำการเปลี่ยนแปลงโค้ด
- เลื่อนเคอร์เซอร์ไปยังคำสั่ง switch จากส่วนก่อนหน้า
- คลิกหลอดไฟเพื่อดูการสนับสนุนที่พร้อมให้บริการ
- เลือกความช่วยเหลือแปลงเป็นนิพจน์ Switch

โค้ดเวอร์ชันใหม่มีลักษณะดังนี้
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 และใช้ => เพื่อแยกรูปแบบออกจากเนื้อหาของเคส นิพจน์ Switch จะคืนค่าและใช้ได้ทุกที่ที่ใช้นิพจน์ได้ ซึ่งแตกต่างจากคำสั่ง Switch
11. ใช้รูปแบบออบเจ็กต์
Dart เป็นภาษาเชิงวัตถุ ดังนั้นรูปแบบจึงใช้ได้กับออบเจ็กต์ทั้งหมด ในขั้นตอนนี้ คุณจะเปิดรูปแบบออบเจ็กต์และแยกโครงสร้างพร็อพเพอร์ตี้ออบเจ็กต์เพื่อปรับปรุงตรรกะการแสดงวันที่ของ UI
ดึงพร็อพเพอร์ตี้จากรูปแบบออบเจ็กต์
ในส่วนนี้ คุณจะปรับปรุงวิธีแสดงวันที่แก้ไขล่าสุดโดยใช้รูปแบบ
- เพิ่ม
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
แต่ละกรณีของนิพจน์ switch ใช้รูปแบบออบเจ็กต์ที่ตรงกันโดยการเรียกใช้ตัวรับค่าในพร็อพเพอร์ตี้ inDays และ isNegative ของออบเจ็กต์ ไวยากรณ์ดูเหมือนจะสร้างออบเจ็กต์ Duration แต่จริงๆ แล้วเป็นการเข้าถึงฟิลด์ในออบเจ็กต์ difference
3 กรณีแรกใช้รูปแบบย่อยคงที่ 0, 1 และ -1 เพื่อจับคู่พร็อพเพอร์ตี้ออบเจ็กต์ inDays และแสดงผลสตริงที่เกี่ยวข้อง
2 กรณีสุดท้ายจะจัดการระยะเวลาที่เกินกว่าวันนี้ เมื่อวาน และพรุ่งนี้
- หากพร็อพเพอร์ตี้
isNegativeตรงกับรูปแบบค่าคงที่บูลีนtrueซึ่งหมายความว่าวันที่แก้ไขอยู่ในอดีต ระบบจะแสดงวันก่อน - หากกรณีดังกล่าวไม่พบความแตกต่าง ระยะเวลาต้องเป็นจำนวนวันที่เป็นบวก (ไม่จำเป็นต้องยืนยันอย่างชัดเจนด้วย
isNegative: false) ดังนั้นวันที่แก้ไขจึงอยู่ในอนาคตและแสดงเป็น วันนับจากนี้
เพิ่มตรรกะการจัดรูปแบบสำหรับสัปดาห์
- เพิ่ม 2 กรณีใหม่ลงในฟังก์ชันการจัดรูปแบบเพื่อระบุระยะเวลาที่นานกว่า 7 วัน เพื่อให้ UI แสดงระยะเวลาดังกล่าวเป็นสัปดาห์ได้
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',
};
}
โค้ดนี้จะแนะนำเงื่อนไขการ์ด
- Guard Clause ใช้คีย์เวิร์ด
whenหลังจากรูปแบบเคส - ซึ่งใช้ได้ในกรณี if, คำสั่ง switch และนิพจน์ switch
- โดยจะเพิ่มเงื่อนไขลงในรูปแบบหลังจากที่ตรงกันแล้วเท่านั้น
- หากอนุประโยคการ์ดประเมินค่าเป็นเท็จ รูปแบบทั้งหมดจะถูกปฏิเสธ และการดำเนินการจะไปยังเคสถัดไป
เพิ่มวันที่ที่จัดรูปแบบใหม่ลงใน UI
- สุดท้าย ให้อัปเดตเมธอด
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]);
},
),
),
],
),
);
}
}
- วิธีโหลดด่วนเพื่อดูการเปลี่ยนแปลงในแอป

12. ปิดผนึกคลาสสำหรับการสลับที่ครอบคลุม
โปรดสังเกตว่าคุณไม่ได้ใช้ไวลด์การ์ดหรือเคสเริ่มต้นที่ส่วนท้ายของสวิตช์สุดท้าย แม้ว่าแนวทางปฏิบัติแนะนำคือการรวมเคสสำหรับค่าที่อาจไม่ตรงกันเสมอ แต่ในตัวอย่างง่ายๆ เช่นนี้ก็ไม่เป็นไร เนื่องจากคุณทราบว่าเคสที่กำหนดนั้นครอบคลุมค่าที่เป็นไปได้ทั้งหมด inDays
เมื่อจัดการทุกกรณีใน Switch แล้ว จะเรียกว่า exhaustive switch เช่น การเปิดboolประเภทหนึ่งจะครอบคลุมทั้งหมดเมื่อมีกรณีสำหรับtrueและfalse การเปิดใช้ประเภท enum จะครอบคลุมทุกกรณีเมื่อมีเคสสำหรับค่าแต่ละค่าของ Enum ด้วย เนื่องจาก Enum แสดงถึงจำนวนคงที่ของค่าคงที่
Dart 3 ขยายการตรวจสอบความครอบคลุมไปยังออบเจ็กต์และลำดับชั้นของคลาสด้วยตัวแก้ไขคลาสใหม่ sealed ปรับโครงสร้างBlockคลาสเป็นคลาสแม่ที่ปิดผนึก
สร้างคลาสย่อย
- ใน
data.dartให้สร้างคลาสใหม่ 3 คลาส ได้แก่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 เป็นนิพจน์ switch ที่แสดงผลคลาสย่อยที่สอดคล้องกับ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คีย์เวิร์ดเป็นตัวแก้ไขคลาส ซึ่งหมายความว่าคุณขยายหรือใช้คลาสนี้ได้เฉพาะในไลบรารีเดียวกัน เนื่องจากเครื่องมือวิเคราะห์ทราบประเภทข้อมูลย่อยของคลาสนี้ จึงจะรายงานข้อผิดพลาดหากสวิตช์ไม่ครอบคลุมประเภทข้อมูลย่อยใดประเภทหนึ่งและไม่ครอบคลุมทั้งหมด
ใช้นิพจน์สวิตช์เพื่อแสดงวิดเจ็ต
- อัปเดตคลาส
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 เองและจับคู่กับรูปแบบออบเจ็กต์ที่แสดงคลาสย่อยของออบเจ็กต์นั้น โดยดึงพร็อพเพอร์ตี้ของออบเจ็กต์ในกระบวนการนี้
โปรแกรมวิเคราะห์ Dart สามารถตรวจสอบได้ว่ามีการจัดการแต่ละคลาสย่อยในนิพจน์ switch เนื่องจากคุณทำให้ Block เป็นคลาสที่ปิดผนึก
นอกจากนี้ โปรดทราบว่าการใช้นิพจน์ Switch ที่นี่จะช่วยให้คุณส่งผลลัพธ์ไปยังองค์ประกอบ child ได้โดยตรง ซึ่งต่างจากคำสั่ง Return แยกต่างหากที่จำเป็นก่อนหน้านี้
- โหลดซ้ำด่วนเพื่อดูข้อมูล JSON ของช่องทําเครื่องหมายที่แสดงเป็นครั้งแรก

13. ขอแสดงความยินดี
คุณได้ทดลองใช้รูปแบบ ระเบียน สวิตช์และเคสที่ได้รับการปรับปรุง รวมถึงคลาสที่ปิดผนึกเรียบร้อยแล้ว คุณได้อธิบายข้อมูลจำนวนมาก แต่ก็ยังเป็นเพียงการกล่าวถึงฟีเจอร์เหล่านี้แบบผิวเผินเท่านั้น ดูข้อมูลเพิ่มเติมเกี่ยวกับรูปแบบได้ที่ข้อกำหนดของฟีเจอร์
รูปแบบประเภทต่างๆ บริบทต่างๆ ที่รูปแบบปรากฏ และการซ้อนกันของรูปแบบย่อยที่อาจเกิดขึ้นทำให้ความเป็นไปได้ในลักษณะการทำงานดูเหมือนจะไม่มีที่สิ้นสุด แต่จะมองเห็นได้ง่าย
คุณสามารถนึกถึงวิธีต่างๆ ในการแสดงเนื้อหาใน Flutter โดยใช้รูปแบบ การใช้รูปแบบช่วยให้คุณดึงข้อมูลได้อย่างปลอดภัยเพื่อสร้าง UI ด้วยโค้ดเพียงไม่กี่บรรทัด
สิ่งต่อไปที่ควรทำ
- ดูเอกสารประกอบเกี่ยวกับรูปแบบ ระเบียน สวิตช์และเคสที่ได้รับการปรับปรุง รวมถึงตัวแก้ไขคลาสในส่วนภาษาของเอกสารประกอบ Dart
เอกสารอ้างอิง
ดูโค้ดตัวอย่างแบบเต็มทีละขั้นตอนได้ในที่เก็บ flutter/codelabs
ดูข้อกำหนดโดยละเอียดสำหรับฟีเจอร์ใหม่แต่ละรายการได้ในเอกสารการออกแบบต้นฉบับ