1. บทนำ
Dart 3 เพิ่มรูปแบบเข้ามาในภาษา ซึ่งเป็นหมวดหมู่ไวยากรณ์ใหม่ที่สำคัญ นอกจากวิธีใหม่ในการเขียนโค้ด Dart แล้ว ยังมีการเพิ่มประสิทธิภาพภาษาอื่นๆ อีกหลายอย่าง ซึ่งรวมถึง
- ระเบียนสำหรับรวมข้อมูลประเภทต่างๆ
- ตัวแก้ไขระดับชั้นเรียนสำหรับควบคุมการเข้าถึง และ
- นิพจน์ Switch และคำสั่ง if-case ใหม่
ฟีเจอร์เหล่านี้จะขยายตัวเลือกที่คุณมีเมื่อเขียนโค้ด Dart ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีใช้เมธอดเหล่านี้เพื่อทำให้โค้ดกระชับ มีประสิทธิภาพ และยืดหยุ่นมากขึ้น
โค้ดแล็บนี้ถือว่าคุณคุ้นเคยกับ 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 ในโค้ดแล็บนี้ คุณจะลดความซับซ้อนของ Use Case ที่สมจริงมากขึ้นด้วยการจําลองข้อมูล 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ให้เรียกใช้เมธอดmetadatagetter ในเมธอด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}')),
],
),
);
}
}
เมธอด getter ของ metadata จะแสดงผลระเบียนซึ่งกำหนดให้กับตัวแปรภายใน metadataRecord ระเบียนเป็นวิธีที่ง่ายและเบาสบายในการแสดงผลหลายค่าจากการเรียกใช้ฟังก์ชันครั้งเดียวและกำหนดค่าให้กับตัวแปร
หากต้องการเข้าถึงฟิลด์แต่ละรายการที่ประกอบขึ้นในระเบียนนั้น คุณสามารถใช้ไวยากรณ์ getter ในตัวของเรกคอร์ด
- หากต้องการรับฟิลด์ตำแหน่ง (ฟิลด์ที่ไม่มีชื่อ เช่น
title) ให้ใช้ตัวรับในระเบียน ซึ่งจะแสดงเฉพาะช่องที่ไม่มีชื่อ - ฟิลด์ที่มีชื่อ เช่น
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. จับคู่และแยกโครงสร้างด้วยรูปแบบ
ระเบียนสามารถรวบรวมข้อมูลประเภทต่างๆ ได้อย่างมีประสิทธิภาพและส่งต่อได้อย่างง่ายดาย ตอนนี้มาปรับปรุงโค้ดโดยใช้รูปแบบกัน
รูปแบบแสดงโครงสร้างที่รับค่าได้อย่างน้อย 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เวอร์ชันเก่าด้วย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) - ข้อมูลไม่ใช่ค่า Null ซึ่งได้รับการยืนยันโดยนัยในการตรวจสอบก่อนหน้านี้
อ่านค่า 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. เตรียมแอปให้พร้อมสำหรับรูปแบบเพิ่มเติม
จนถึงตอนนี้ คุณได้จัดการกับข้อมูล JSON ส่วน metadata ในขั้นตอนนี้ คุณจะปรับแต่งตรรกะทางธุรกิจอีกเล็กน้อยเพื่อจัดการข้อมูลในรายการ 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 ซึ่งไม่ได้อยู่ในรูปแบบก็ตาม เนื่องจากเมื่อคุณใช้รูปแบบประเภทเหล่านี้ (เรียกว่า "รูปแบบแผนที่") รูปแบบจะสนใจเฉพาะสิ่งที่คุณกำหนดไว้ในรูปแบบเท่านั้น และจะไม่สนใจสิ่งอื่นๆ ในข้อมูล
แสดงรายการออบเจ็กต์บล็อก
- ถัดไป ให้เพิ่มฟังก์ชันใหม่
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 ให้เป็น List ใหม่ชื่อ blocksJson (หากไม่มีรูปแบบ คุณต้องใช้เมธอด toList() เพื่อแคสต์)
ลิเทอรัลลิสต์มี collection for เพื่อเติมออบเจ็กต์ Block ลงในลิสต์ใหม่
ส่วนนี้จะไม่แสดงฟีเจอร์ที่เกี่ยวข้องกับรูปแบบที่คุณยังไม่ได้ลองใช้ใน Codelab นี้ ในขั้นตอนถัดไป คุณจะเตรียมเรนเดอร์รายการใน UI
9. ใช้รูปแบบเพื่อแสดงเอกสาร
ตอนนี้คุณแยกโครงสร้างและจัดโครงสร้างข้อมูล JSON อีกครั้งเรียบร้อยแล้วโดยใช้คำสั่ง if-case และรูปแบบที่โต้แย้งได้ แต่เงื่อนไข "if" เป็นเพียงหนึ่งในการเพิ่มประสิทธิภาพโครงสร้างการควบคุมโฟลว์ที่มาพร้อมกับรูปแบบ ตอนนี้คุณใช้ความรู้เกี่ยวกับรูปแบบที่โต้แย้งได้กับคำสั่ง 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
- คำสั่งเคสแรกใช้รูปแบบสตริงคงที่ รูปแบบจะตรงกันหาก
block.typeเท่ากับค่าคงที่h1 - คำสั่งเคสที่ 2 ใช้รูปแบบตรรกะ OR ที่มีรูปแบบสตริงคงที่ 2 รายการเป็นรูปแบบย่อย รูปแบบจะตรงกันหาก
block.typeตรงกับรูปแบบย่อยpหรือcheckbox
- กรณีสุดท้ายคือ รูปแบบไวลด์การ์ด
_ไวลด์การ์ดใน Switch Case จะจับคู่กับทุกค่า เงื่อนไขเหล่านี้จะทํางานเหมือนกับประโยคdefaultซึ่งยังคงได้รับอนุญาตในคำสั่ง 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 เพื่อให้ใช้เงื่อนไขได้ในหลายๆ ที่ ชุดกรณีสามารถระบุค่าให้กับการกําหนดตัวแปรหรือคำสั่งแสดงผลได้โดยตรง
แปลงคำสั่ง 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 และใช้ => เพื่อแยกรูปแบบออกจากเนื้อหาของ 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',
};
}
เมธอดนี้จะแสดงผลนิพจน์ Switch ที่เปิดค่า difference ซึ่งเป็นออบเจ็กต์ Duration ซึ่งแสดงระยะเวลาระหว่าง today กับค่า modified จากข้อมูล JSON
แต่ละกรณีของนิพจน์ Switch ใช้รูปแบบออบเจ็กต์ที่ตรงกันโดยการเรียกใช้ Geter ในพร็อพเพอร์ตี้ 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',
};
}
โค้ดนี้จะแสดงเงื่อไขที่ใช้ควบคุม
- อนุประโยคที่ใช้คีย์เวิร์ด
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 เป็นนิพจน์ 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 คือตัวแก้ไขคลาส ซึ่งหมายความว่าคุณจะขยายหรือใช้คลาสนี้ในไลบรารีเดียวกันเท่านั้น เนื่องจากเครื่องมือวิเคราะห์ทราบประเภทย่อยของคลาสนี้ จึงจะรายงานข้อผิดพลาดหากสวิตช์ไม่ครอบคลุมประเภทย่อยใดประเภทหนึ่งและไม่ได้ครอบคลุมทั้งหมด
ใช้นิพจน์ Switch เพื่อแสดงวิดเจ็ต
- อัปเดตคลาส
BlockWidgetในmain.dartด้วยนิพจน์ Switch ที่ใช้รูปแบบออบเจ็กต์สำหรับแต่ละกรณี ดังนี้
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
ดูข้อกำหนดเชิงลึกของฟีเจอร์ใหม่แต่ละรายการได้ในเอกสารการออกแบบต้นฉบับ