1. บทนำ
Dart 3 ได้แนะนำรูปแบบสำหรับภาษา ซึ่งเป็นหมวดหมู่ไวยากรณ์ใหม่ที่สำคัญ นอกเหนือจากวิธีใหม่ในการเขียนโค้ดของ Dart แล้ว ยังมีการเพิ่มประสิทธิภาพภาษาอื่นๆ อีกมากมาย ซึ่งรวมถึง
- ระเบียน เพื่อรวมกลุ่มข้อมูลประเภทต่างๆ
- ตัวแก้ไขคลาสสำหรับควบคุมการเข้าถึง และ
- นิพจน์สวิตช์และคำสั่ง if-case ใหม่
ฟีเจอร์เหล่านี้จะช่วยเพิ่มตัวเลือกให้คุณเมื่อเขียนโค้ด Dart คุณจะได้ทราบวิธีใช้โค้ดแล็บนี้เพื่อทำให้โค้ดกะทัดรัด คล่องตัว และยืดหยุ่นยิ่งขึ้น
Codelab นี้จะสมมติว่าคุณมีความคุ้นเคยกับ Flutter และ Dart อยู่บ้าง ถ้าคุณรู้สึกว่าสนิมเล็กน้อย ให้ลองทบทวนข้อมูลเบื้องต้นด้วยแหล่งข้อมูลต่อไปนี้:
สิ่งที่คุณจะสร้าง
Codelab นี้จะสร้างแอปพลิเคชันที่แสดงเอกสาร JSON ใน Flutter แอปพลิเคชันจะจำลอง JSON ที่มาจากแหล่งที่มาภายนอก ไฟล์ JSON ประกอบด้วยข้อมูลเอกสาร เช่น วันที่แก้ไข ชื่อ ส่วนหัว และย่อหน้า เขียนโค้ดเพื่อแพ็คข้อมูลอย่างเป็นระเบียบเพื่อให้โอนและคลายการแพคข้อมูลได้ในที่ที่วิดเจ็ต Flutter ต้องการ
จากนั้นคุณจะใช้รูปแบบเพื่อสร้างวิดเจ็ตที่เหมาะสมเมื่อค่าตรงกับรูปแบบดังกล่าว นอกจากนี้ คุณยังจะได้ทราบวิธีใช้รูปแบบเพื่อทำลายข้อมูลลงในตัวแปรภายใน
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างระเบียนที่เก็บค่าหลายค่าที่มีประเภทต่างๆ
- วิธีแสดงผลค่าหลายค่าจากฟังก์ชันโดยใช้ระเบียน
- วิธีใช้รูปแบบในการจับคู่ ตรวจสอบ และทำลายข้อมูลจากระเบียนและออบเจ็กต์อื่นๆ
- วิธีเชื่อมโยงค่าที่ตรงกับรูปแบบกับตัวแปรใหม่หรือที่มีอยู่
- วิธีใช้ความสามารถใหม่ของคำสั่งสวิตช์ เปลี่ยนนิพจน์ และคำสั่งหากเป็นกรณี
- วิธีใช้ประโยชน์จากการตรวจสอบอย่างละเอียดเพื่อดูแลให้ทุกกรณีได้รับการจัดการในคำสั่งสวิตช์หรือนิพจน์สวิตช์
2. ตั้งค่าสภาพแวดล้อมของคุณ
- ติดตั้ง Flutter SDK
- ตั้งค่าตัวแก้ไข เช่น โค้ด Visual Studio (VS Code)
- ทำตามขั้นตอนการตั้งค่าแพลตฟอร์มสำหรับแพลตฟอร์มเป้าหมายอย่างน้อย 1 แพลตฟอร์ม (iOS, Android, เดสก์ท็อป หรือเว็บเบราว์เซอร์)
3. สร้างโปรเจ็กต์
ก่อนที่จะเจาะลึกเกี่ยวกับรูปแบบ ระเบียน และฟีเจอร์ใหม่อื่นๆ โปรดใช้เวลาสักครู่เพื่อสร้างโปรเจ็กต์ Flutter แบบง่ายๆ ที่คุณเขียนโค้ดทั้งหมดเอง
สร้างโปรเจ็กต์ Flutter
- ใช้คำสั่ง
flutter create
เพื่อสร้างโปรเจ็กต์ใหม่ชื่อpatterns_codelab
แฟล็ก--empty
จะป้องกันไม่ให้สร้างแอปตัวนับมาตรฐานในไฟล์lib/main.dart
ซึ่งคุณจะต้องนำออกอยู่ดี
flutter create --empty patterns_codelab
- จากนั้นเปิดไดเรกทอรี
patterns_codelab
โดยใช้โค้ด VS
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 ใน Codelab นี้ คุณสามารถลดความซับซ้อนของกรณีการใช้งานที่สมจริงยิ่งขึ้นโดยการจำลองข้อมูล JSON ขาเข้าด้วยสตริงหลายบรรทัดในตัวแปร documentJson
ข้อมูล JSON ได้รับการกำหนดไว้ในคลาส Document
คุณเพิ่มฟังก์ชันที่ส่งคืนข้อมูลจาก JSON ที่แยกวิเคราะห์แล้วในภายหลังใน Codelab นี้ คลาสนี้จะกำหนดและเริ่มต้นฟิลด์ _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(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'),
),
],
),
);
}
}
คุณเพิ่มวิดเจ็ต 2 รายการต่อไปนี้ลงในแอป:
DocumentApp
ตั้งค่า Material Design เวอร์ชันล่าสุดสำหรับธีม UIDocumentScreen
แสดงเลย์เอาต์รูปภาพของหน้าเว็บโดยใช้วิดเจ็ตScaffold
- เพื่อให้แน่ใจว่าทุกอย่างทำงานได้อย่างราบรื่น ให้เรียกใช้แอปในเครื่องที่โฮสต์โดยคลิกเรียกใช้และแก้ไขข้อบกพร่อง ตามขั้นตอนต่อไปนี้
- โดยค่าเริ่มต้น Flutter จะเลือกแพลตฟอร์มเป้าหมายที่ใช้ได้ หากต้องการเปลี่ยนแพลตฟอร์มเป้าหมาย ให้เลือกแพลตฟอร์มปัจจุบันบนแถบสถานะ:
คุณควรเห็นเฟรมว่างที่มีองค์ประกอบ title
และ body
กำหนดไว้ในวิดเจ็ต DocumentScreen
ดังนี้
5. สร้างและแสดงผลระเบียน
ในขั้นตอนนี้ คุณจะใช้ระเบียนเพื่อแสดงค่าหลายค่าจากการเรียกใช้ฟังก์ชัน จากนั้นจึงเรียกใช้ฟังก์ชันดังกล่าวในวิดเจ็ต DocumentScreen
เพื่อเข้าถึงค่าและแสดงค่าใน UI
สร้างและแสดงผลระเบียน
- ใน
data.dart
ให้เพิ่มเมธอด Getter ใหม่ไปยังคลาสเอกสารที่ชื่อ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
ให้เรียกใช้เมธอด getter ของ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.
),
),
],
),
);
}
}
เมธอด Getter metadata
จะแสดงผลระเบียน ซึ่งกำหนดให้กับตัวแปรภายใน metadataRecord
ระเบียนเป็นวิธีที่ง่ายและสะดวกในการแสดงผลค่าหลายค่าจากการเรียกใช้ฟังก์ชันเดียว แล้วกำหนดให้กับตัวแปร
หากต้องการเข้าถึงช่องแต่ละช่องที่อยู่ในระเบียนนั้น คุณสามารถใช้ระเบียน ไวยากรณ์ Getter ในตัว
- หากต้องการได้ช่องตำแหน่ง (ช่องที่ไม่มีชื่อ เช่น
title
) ให้ใช้ Getter$<num>
ในระเบียน การดำเนินการนี้จะแสดงเฉพาะช่องที่ไม่มีชื่อ - ช่องที่มีชื่ออย่างเช่น
modified
จะไม่มี Getter ตําแหน่ง คุณจึงใช้ชื่อของช่องโดยตรงได้ เช่นmetadataRecord.modified
หากต้องการกำหนดชื่อของ Getter สำหรับช่องตำแหน่ง ให้เริ่มต้นที่ $1
แล้วข้ามฟิลด์ที่มีชื่อ เช่น
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- โหลดซ้ำแบบ Hot เพื่อดูค่า JSON ที่แสดงในแอป ปลั๊กอิน VS Code Dart จะโหลดใหม่ทุกครั้งที่คุณบันทึกไฟล์
คุณจะเห็นว่าแต่ละช่องได้รักษาประเภทของช่องไว้
- เมธอด
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
),
body: Column(
children: [
Center(
child: Text(
'Last modified $modified', // Modify
),
),
],
),
);
}
}
รูปแบบระเบียน (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
แทน
- โหลดซ้ำแบบ Hot เพื่อดูผลลัพธ์แบบเดียวกับในขั้นตอนก่อนหน้า ลักษณะการทำงานจะเหมือนกันทุกประการ คุณได้ทำให้โค้ดกระชับยิ่งขึ้น
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 // 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 ประเภทใหม่ (เปิดตัวใน Dart 3) ซึ่งก็คือ if-case ส่วนเนื้อหาของเคสจะทำงานก็ต่อเมื่อรูปแบบเคสตรงกับข้อมูลใน _json
การจับคู่นี้จะทําการตรวจสอบเหมือนกับที่คุณเขียนไว้ใน metadata
เวอร์ชันแรกเพื่อตรวจสอบ JSON ขาเข้า โค้ดนี้จะตรวจสอบความถูกต้องดังต่อไปนี้
_json
เป็นประเภทแผนที่_json
มีคีย์metadata
_json
มีข้อมูล_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()
ใช้กรณีเดียวกันกับรูปแบบแผนที่ที่คุณเคยเห็นมาก่อน
โปรดสังเกตว่า json
ตรงกับรูปแบบแผนที่ แม้ว่าหนึ่งในคีย์คือ checked
จะไม่ถูกนำมาคำนวณในรูปแบบนี้ รูปแบบแผนที่จะละเว้นรายการใดๆ ในออบเจ็กต์แผนที่ไม่ได้รับการนำมาพิจารณาในรูปแบบนี้อย่างชัดแจ้ง
แสดงรายการออบเจ็กต์บล็อก
- จากนั้นเพิ่มฟังก์ชันใหม่
getBlocks()
ลงในชั้นเรียนDocument
getBlocks()
แยกวิเคราะห์ 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()
เพื่อแคสต์)
รายการแบบลิเทอรัลมีคอลเล็กชันสำหรับ เพื่อเติมออบเจ็กต์ Block
รายการใหม่ในรายการ
ส่วนนี้จะไม่เพิ่มฟีเจอร์ที่เกี่ยวข้องกับรูปแบบซึ่งคุณยังไม่ได้ทดลองใช้ใน Codelab นี้ ในขั้นตอนถัดไป คุณจะต้องเตรียมแสดงผลรายการใน UI
9. ใช้รูปแบบเพื่อแสดงเอกสาร
ตอนนี้คุณถอดรหัสและเขียนข้อมูล 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,
),
);
}
}
คำสั่งสวิตช์ในเมธอด build
จะเปลี่ยนช่อง type
ของออบเจ็กต์ block
- คำสั่งอักษรตัวพิมพ์เล็ก/ใหญ่ใช้รูปแบบสตริงคงที่ รูปแบบจะจับคู่หาก
block.type
เท่ากับค่าคงที่h1
- คำสั่งกรณีที่ 2 ใช้ตรรกะหรือรูปแบบที่มีรูปแบบสตริงคงที่ 2 รายการเป็นรูปแบบย่อย รูปแบบจะจับคู่หาก
block.type
ตรงกับรูปแบบย่อยp
หรือcheckbox
- กรณีสุดท้ายคือรูปแบบไวลด์การ์ด
_
ไวลด์การ์ดในกรณีสวิตช์จะจับคู่กับส่วนอื่นๆ ทั้งหมด ซึ่งยังทำงานเหมือนกับวลีdefault
ซึ่งยังได้รับอนุญาตให้ใช้ในคำสั่งสวิตช์ (มีรายละเอียดมากกว่านี้เล็กน้อย)
สามารถใช้รูปแบบไวลด์การ์ดได้ทุกที่ที่อนุญาตรูปแบบ เช่น ในรูปแบบการประกาศตัวแปร: 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
และ case
Dart ใช้นิพจน์สวิตช์เพื่อให้สามารถใช้ได้ในที่อื่นๆ มากขึ้น กรณีต่างๆ สามารถให้ค่าโดยตรงต่อการกำหนดตัวแปรหรือคำสั่งส่งกลับ
แปลงคำสั่งสวิตช์เป็นนิพจน์สวิตช์
เครื่องมือวิเคราะห์ของ DART มีเครื่องมือช่วยเหลือเพื่อช่วยคุณเปลี่ยนแปลงโค้ด
- เลื่อนเคอร์เซอร์ไปที่คำสั่งสวิตช์จากส่วนก่อนหน้า
- คลิกหลอดไฟเพื่อดูผู้ช่วยที่มี
- เลือกตัวช่วยแปลงเป็นนิพจน์สลับ
โค้ดรุ่นใหม่นี้มีลักษณะดังนี้
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,
),
);
}
}
นิพจน์สวิตช์มีลักษณะคล้ายกับคำสั่งสวิตช์ แต่จะลบคีย์เวิร์ด 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
นิพจน์สวิตช์แต่ละกรณีกำลังใช้รูปแบบวัตถุที่ตรงกันโดยเรียกใช้ Getters ในคุณสมบัติของวัตถุ 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
หลังรูปแบบอักษรตัวพิมพ์เล็ก/ใหญ่ - โดยใช้ได้ในกรณี เปลี่ยนคำสั่ง และนิพจน์สลับ
- ระบบจะเพิ่มเงื่อนไขลงในรูปแบบหลังจากจับคู่แล้วเท่านั้น
- หากเงื่อนไขการป้องกันประเมินเป็น "เท็จ" ระบบจะปฏิเสธรูปแบบทั้งหมด และดำเนินการไปยังกรณีถัดไป
เพิ่มวันที่รูปแบบใหม่ใน 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]);
},
),
),
],
),
);
}
}
- โหลดซ้ำแบบ Hot เพื่อดูการเปลี่ยนแปลงในแอป
12. ปิดชั้นเรียนเพื่อให้การเปลี่ยนอุปกรณ์มีความครบถ้วนสมบูรณ์
โปรดสังเกตว่าคุณไม่ได้ใช้ไวลด์การ์ดหรือตัวพิมพ์เริ่มต้นในตอนท้ายของสวิตช์ล่าสุด แม้ว่าแนวทางปฏิบัติที่ดีคือการใส่ตัวพิมพ์เล็กหรือใหญ่สำหรับค่าต่างๆ เสมอ แต่ก็อาจเป็นตัวอย่างง่ายๆ อย่างเช่นในกรณีนี้ เนื่องจากคุณทราบกรณีที่คุณกำหนดเป็นบัญชีสำหรับ ค่าที่เป็นไปได้ทั้งหมด inDays
ที่เป็นไปได้
เมื่อมีการจัดการสวิตช์ทุกกรณี จะถือว่าเป็นสวิตช์ที่สมบูรณ์ ตัวอย่างเช่น การเปิดใช้งานประเภท 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 เป็นนิพจน์สวิตช์ที่ส่งคืนคลาสย่อยที่สอดคล้องกับ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
คือตัวแก้ไขคลาสที่หมายความว่าคุณจะขยายหรือใช้งานคลาสนี้ได้เฉพาะในไลบรารีเดียวกัน เนื่องจากเครื่องมือวิเคราะห์รู้จักประเภทย่อยของคลาสนี้ จึงรายงานข้อผิดพลาดหากสวิตช์ไม่ครอบคลุม 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
แล้วจับคู่กับรูปแบบวัตถุที่แสดงถึงคลาสย่อยของออบเจ็กต์ดังกล่าว ซึ่งจะดึงคุณสมบัติของออบเจ็กต์ในกระบวนการนี้
เครื่องมือวิเคราะห์ของ DART สามารถตรวจสอบว่าคลาสย่อยแต่ละรายการมีการจัดการในนิพจน์สวิตช์ เนื่องจากคุณกำหนดให้ Block
เป็นคลาสที่ปิดผนึก
โปรดทราบว่าการใช้นิพจน์สวิตช์ที่นี่จะให้คุณส่งผลลัพธ์ไปยังองค์ประกอบ child
โดยตรง ซึ่งตรงข้ามกับคำสั่งส่งกลับแบบแยกต่างหากที่ต้องใช้ก่อนหน้านี้
- การโหลดซ้ำแบบ Hot ใหม่เพื่อดูข้อมูล JSON ของช่องทำเครื่องหมายที่แสดงเป็นครั้งแรก:
13. ขอแสดงความยินดี
คุณทดลองกับรูปแบบ บันทึก สวิตช์และเคสที่เพิ่มประสิทธิภาพ ตลอดจนคลาสที่ปิดผนึกได้สำเร็จ คุณให้ข้อมูลไปมากมาย แต่เพียงแค่มีรอยขีดข่วนเพียงเล็กน้อยของฟีเจอร์เหล่านี้ ดูข้อมูลเพิ่มเติมเกี่ยวกับรูปแบบได้ที่ข้อกำหนดฟีเจอร์
รูปแบบที่แตกต่างกัน บริบทที่แตกต่างกันที่จะปรากฏได้ และการฝังรูปแบบย่อยที่เป็นไปได้ ทำให้ความเป็นไปได้ในพฤติกรรมต่างๆ ดูเหมือนจะไม่มีที่สิ้นสุด แต่มองเห็นได้ง่าย
คุณอาจลองจินตนาการถึงวิธีต่างๆ ในการแสดงเนื้อหาใน Flutter โดยใช้รูปแบบ เมื่อใช้รูปแบบ คุณสามารถดึงข้อมูลได้อย่างปลอดภัยเพื่อสร้าง UI โดยใช้โค้ดเพียงไม่กี่บรรทัด
สิ่งที่ต้องทำต่อไป
- ดูเอกสารประกอบเกี่ยวกับรูปแบบ ระเบียน สวิตช์และเคสที่ปรับปรุงแล้ว รวมถึงตัวปรับแต่งคลาสได้ในส่วนภาษาของเอกสารประกอบของ Dart
เอกสารอ้างอิง
ดูโค้ดตัวอย่างทั้งหมดแบบทีละขั้นตอนในที่เก็บ flutter/codelabs
สำหรับข้อมูลจำเพาะโดยละเอียดของฟีเจอร์ใหม่แต่ละรายการ โปรดดูเอกสารการออกแบบดั้งเดิม