1. 簡介
Dart 3 為語言提供模式,這種語言是新的主要文法類別。除了這種新方法來編寫 Dart 程式碼,還有幾項語言強化功能,包括:
- 記錄,用於合併不同類型的資料。
- 用於控制存取權的類別修飾符
- 新增切換運算式和 if-case 陳述式。
這些功能可讓您在編寫 Dart 程式碼時享有更多選擇。在本程式碼研究室中,您將瞭解如何使用這些功能,讓程式碼更簡潔、更精簡且更有彈性。
本程式碼研究室假設您對 Flutter 和 Dart 有一定程度的瞭解。如果你覺得有點信任感,不妨利用下列資源複習基本知識:
建構項目
本程式碼研究室會建立一個應用程式,以在 Flutter 中顯示 JSON 文件。應用程式會模擬來自外部來源的 JSON。JSON 檔案包含文件資料,例如修改日期、標題、標頭和段落。您可以編寫程式碼,將資料妥善封裝至記錄中,以便在任何 Flutter 小工具中傳輸及解壓縮。
然後,如果值符合該模式,請使用模式來建構適當的小工具。您也會瞭解如何使用模式將資料解構為本機變數。
課程內容
- 如何建立可以儲存不同類型值的記錄。
- 如何使用記錄從函式傳回多個值。
- 如何使用模式比對、驗證記錄和其他物件中的資料,並加以銷毀。
- 如何將模式比對值繫結至新的或現有變數。
- 如何使用新的 Switch 陳述式功能、切換運算式和 if-case 陳述式。
- 如何利用完整性檢查功能,確保每個案件都可在 Switch 陳述式或切換運算式中處理。
2. 設定環境
- 安裝 Flutter SDK。
- 設定編輯器,例如 Visual Studio Code (VS Code)。
- 完成平台設定步驟,設定至少一個目標平台 (iOS、Android、電腦或網路瀏覽器)。
3. 建立專案
在深入研究模式、記錄和其他新功能之前,請先花點時間建立簡單的 Flutter 專案,以便用於編寫所有程式碼。
建立 Flutter 專案
- 使用
flutter create
指令建立名為patterns_codelab
的新專案。--empty
旗標會防止在lib/main.dart
檔案中建立標準計數器應用程式,您仍須移除。
flutter create --empty patterns_codelab
- 接著,使用 VS Code 開啟
patterns_codelab
目錄。
code patterns_codelab
設定 SDK 最低版本
- 為專案設定 SDK 版本限制,以使用 Dart 3 以上版本。
pubspec.yaml
environment:
sdk: ^3.0.0
4. 設定專案
在這個步驟中,您會建立或更新兩個 Dart 檔案:
- 包含應用程式小工具的
main.dart
檔案;以及 - 提供應用程式資料的
data.dart
檔案。
您會在後續步驟中繼續修改這兩個檔案。
定義應用程式資料
- 建立新檔案
lib/data.dart
,並加入下列程式碼:
lib/data.dart
import 'dart:convert';
class Document {
final Map<String, Object?> _json;
Document() : _json = jsonDecode(documentJson);
}
const documentJson = '''
{
"metadata": {
"title": "My Document",
"modified": "2023-05-10"
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
{
"type": "p",
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
},
{
"type": "checkbox",
"checked": false,
"text": "Learn Dart 3"
}
]
}
''';
假設程式會從外部來源接收資料,例如 I/O 串流或 HTTP 要求。在本程式碼研究室中,您可以使用 documentJson
變數中的多行字串模擬傳入的 JSON 資料,藉此簡化更真實的用途。
JSON 資料是在 Document
類別中定義。稍後,在這個程式碼研究室中,您將新增函式,以從剖析的 JSON 傳回資料。這個類別會在其建構函式中定義並初始化 _json
欄位。
執行應用程式
flutter create
指令會在預設的 Flutter 檔案結構中建立 lib/main.dart
檔案。
- 如要為應用程式建立起點,請將
main.dart
的內容替換為以下程式碼:
lib/main.dart
import 'package:flutter/material.dart';
import 'data.dart';
void main() {
runApp(const DocumentApp());
}
class DocumentApp extends StatelessWidget {
const DocumentApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: DocumentScreen(
document: Document(),
),
);
}
}
class DocumentScreen extends StatelessWidget {
final Document document;
const DocumentScreen({
required this.document,
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title goes here'),
),
body: const Column(
children: [
Center(
child: Text('Body goes here'),
),
],
),
);
}
}
你已將以下兩個小工具新增至應用程式:
DocumentApp
會設定最新版本的 Material Design,為 UI 設定主題。DocumentScreen
會使用Scaffold
小工具提供網頁的視覺版面配置。
- 如要確保一切運作順暢,請按一下「Run and Debug」(執行及偵錯),在主體機器上執行應用程式:
- 根據預設,Flutter 會選擇可用的目標平台。如要變更目標平台,請在狀態列中選取目前的平台:
您應該會看到空白頁框,其中包含 DocumentScreen
小工具中定義的 title
和 body
元素:
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.
}
這個函式的傳回類型是有兩個欄位的記錄,一個為 String
類型,另一個為 DateTime
類型。
回傳敘述會用括號 ((title, modified: now)
) 括住兩個值,藉此建立新記錄。
第一個欄位是位置且未命名,第二個欄位的名稱為 modified
。
存取記錄欄位
- 在
DocumentScreen
小工具中,呼叫build
方法中的metadata
getter 方法,以便取得記錄並存取其值:
lib/main.dart
class DocumentScreen extends StatelessWidget {
final Document document;
const DocumentScreen({
required this.document,
super.key,
});
@override
Widget build(BuildContext context) {
final metadataRecord = document.metadata; // Add this line.
return Scaffold(
appBar: AppBar(
title: Text(metadataRecord.$1), // Modify this line,
),
body: Column(
children: [
Center(
child: Text(
'Last modified ${metadataRecord.modified}', // And this one.
),
),
],
),
);
}
}
metadata
getter 方法會傳回一筆指派給本機變數 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
- 熱重新載入畫面,查看應用程式中顯示的 JSON 值。每次儲存檔案時,VS Code Dart 外掛程式就會熱載。
您可以看到每個欄位實際上都有保留類型。
Text()
方法會使用 String 做為第一個引數。modified
欄位是 DateTime,並且使用字串內插轉換為String
。
另一個傳回不同類型資料的型別安全方法,是定義較詳細的類別。
6. 比對及破壞模式
記錄可有效收集不同類型的資料,並輕鬆傳遞給他人。現在,請使用模式改善程式碼。
模式代表一或多個值可採用的結構,例如藍圖。模式會比較實際值,以判斷其是否相符。
某些模式在比對相符時,會從相符值提取資料來「解構」。解構函式可讓您解壓縮物件的值,並指派給本機變數,或是進一步比對物件值。
將記錄拆解為本機變數
- 重構
DocumentScreen
的build
方法以呼叫metadata
,並使用該方法初始化模式變數宣告:
lib/main.dart
class DocumentScreen extends StatelessWidget {
final Document document;
const DocumentScreen({
required this.document,
super.key,
});
@override
Widget build(BuildContext context) {
final (title, modified: modified) = document.metadata; // Modify
return Scaffold(
appBar: AppBar(
title: Text(title), // Modify
),
body: Column(
children: [
Center(
child: Text(
'Last modified $modified', // Modify
),
),
],
),
);
}
}
記錄模式 (title, modified: modified)
包含兩個變數模式,且這些模式與 metadata
傳回的記錄欄位相符。
- 運算式會比對子模式,因為結果是包含兩個欄位的記錄,其中一個名稱為
modified
。 - 由於兩者相符,變數宣告模式會破壞運算式、存取其值,並將其繫結至相同類型和名稱的新本機變數:
String title
和DateTime modified
。
當欄位名稱與填入欄位的變數相同時,系統會提供簡寫。重構 DocumentScreen
的 build
方法,如下所示。
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.
}
}
此程式碼驗證資料是否使用模式正確。在後續步驟中,您可以使用模式比對,以較少的程式碼執行相同的驗證。並且會在執行其他作業前執行三項檢查:
- 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 // 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 中導入),如果案例已導入。只有在案例模式與 _json
中的資料相符時,系統才會執行案件主體。這項比對作業會完成您在第一個 metadata
版本中編寫的相同檢查,用來驗證傳入的 JSON。此程式碼會驗證以下項目:
_json
是地圖類型,_json
包含metadata
鍵。_json
非空值。_json['metadata']
也是地圖類型。_json['metadata']
包含title
和modified
索引鍵。title
和localModified
是字串,不是空值。
如果值不相符,則模式拒絕 (拒絕繼續執行) 並繼續執行 else
子句。如果比對成功,模式會從對應中解構 title
和 modified
的值,並將這些值繫結到新的本機變數。
如需完整的模式清單,請參閱功能規格的模式一節。
8. 讓應用程式支援更多模式
到目前為止,您已經處理 JSON 資料的 metadata
部分。在這個步驟中,您將進一步修正商業邏輯,以便處理 blocks
清單的資料並算繪至應用程式。
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
建立可儲存資料的類別
- 在
data.dart
中新增類別Block
,用來讀取及儲存 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 案例。
請注意,即使其中一個鍵 (checked
),json
仍會與對應模式相符。地圖模式會忽略地圖物件中未明確計入模式的任何項目。
傳回 Block 物件清單
- 接下來,請將新函式
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
中繼資料的值轉換為名為 blocksJson
的新 List
(沒有模式,則需要使用 toList()
方法才能投放)。
清單常值包含 集合,以便使用 Block
物件填入新清單。
本節不會介紹本程式碼研究室中未嘗試的任何模式相關功能。在下一個步驟中,您要準備在 UI 中轉譯清單項目。
9. 使用模式顯示文件
現在,您可以使用 if-case 陳述式和可重新產生的模式,成功解構及重組 JSON 資料。不過,如果情況只是控管運用模式的流程結構其中一項強化功能,現在,你可以運用有關可反駁的模式知識來切換陳述式。
使用 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,
),
);
}
}
build
方法中的切換陳述式會切換 block
物件的 type
欄位。
- 第一個 case 陳述式使用常數字串模式。如果
block.type
等於常數值h1
,則模式會配對。 - 第二個案例陳述式使用邏輯或模式,搭配兩個常數字串模式做為子模式。模式會比對
block.type
與p
或checkbox
任一子模式相符。
- 最終的情況是「萬用字元模式」
_
。切換保護殼的萬用字元與所有其他項目相同。這類子句的行為與default
子句相同,但仍可以在 Switch 陳述式中使用 (只是較為詳細)。
您可以在允許特定模式的任何位置使用萬用字元模式,例如在變數宣告模式中:var (title, _) = document.metadata;
在此情況下,萬用字元不會繫結任何變數。該動作會捨棄第二個欄位。
在下一節中,您將瞭解在顯示 Block
物件後,瞭解更多切換功能。
顯示文件內容
在 DocumentScreen
小工具的 build
方法中呼叫 getBlocks()
,建立包含 Block
物件清單的本機變數。
- 將
DocumentationScreen
中現有的build
方法替換為這個版本:
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])
行會為 getBlocks()
方法所傳回的區塊清單中的每個項目建構 BlockWidget
小工具。
- 執行應用程式,您應該會在螢幕上看到區塊:
10. 使用切換運算式
模式會為 switch
和 case
新增許多功能。為了在更多地方使用,Dart 提供了切換運算式。一系列案例可以提供值給變數指派或回傳敘述。
將 Switch 陳述式轉換為 Switch 運算式
Dart 分析工具提供「輔助」功能,方便您變更程式碼。
- 將遊標移至上一節的切換陳述式。
- 只要按一下燈泡,即可查看可用的協助。
- 選取「Convert to Switchexpression」輔助輔助。
此程式碼的新版本如下所示:
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 陳述式類似,但會排除 case
關鍵字,並使用 =>
來區分模式與案件主體。與切換陳述式不同,切換運算式會傳回一個值,可在可使用運算式的任何位置使用。
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
和 JSON 資料中 modified
值之間的時間範圍。
每個切換運算式的情況都使用一種物件模式,該模式會比對物件的屬性 inDays
和 isNegative
來比對。該語法看起來可能是建構 Duration 物件,但實際上是存取 difference
物件的欄位。
前三個情況會使用常數子模式 0
、1
和 -1
,來比對物件屬性 inDays
並傳回對應的字串。
前兩個案例處理的持續時間超過今天、昨天和明天:
- 如果
isNegative
屬性與布林值常數模式true
相符,表示修改日期在過去,則顯示「天前」。 - 如果未偵測到差異,則時間長度必須是正數 (無須向
isNegative: false
明確驗證),因此修改日期會是未來的日期,並顯示從現在起幾天。
新增週別的格式邏輯
- 在格式設定函式中加入兩個新案件,找出超過 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 case、切換陳述式和切換運算式。
- 這些條件只會在相符後的模式中新增條件。
- 如果守衛子句評估為 false,整個模式就會遭到拒絕,繼續執行下一個案例。
在 UI 中新增新格式的日期
- 最後,更新
DocumentScreen
中的build
方法,以使用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
的所有可能值 。
每當切換轉換的過程都發生時,就算是一次全面轉換。舉例來說,如果 bool
類型有 true
和 false
的情況,切換類型就會相當詳盡。如果每個列舉值都存在,切換 enum
類型就會變得詳盡,因為列舉代表常數值的「固定數量」。
Dart 3 使用新的類別修飾符 sealed
,將完整性檢查擴充至物件和類別階層。將 Block
類別重構為密封父類別。
建立子類別
- 在
data.dart
中,建立三個新類別HeaderBlock
、ParagraphBlock
和CheckboxBlock
,擴充Block
:
lib/data.dart
class HeaderBlock extends Block {
final String text;
HeaderBlock(this.text);
}
class ParagraphBlock extends Block {
final String text;
ParagraphBlock(this.text);
}
class CheckboxBlock extends Block {
final String text;
final bool isChecked;
CheckboxBlock(this.text, this.isChecked);
}
這些類別分別對應了原始 JSON 中的不同 type
值:'h1'
、'p'
和 'checkbox'
。
稱霸父類別
- 將
Block
類別標示為sealed
。接著,將 if-case 重構為切換運算式,以傳回與 JSON 中指定的type
相對應的子類別:
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
關鍵字是類別修飾符,也就是說,您只能在同一個程式庫中擴充或實作這個類別。由於分析器知道這個類別的子類型,因此如果切換功能無法涵蓋其中一個子類型,且並未涵蓋所有情況,就會回報錯誤。
使用切換運算式顯示小工具
- 使用切換運算式,更新
main.dart
中的 BlockWidget 類別 (每種情況都使用物件模式):
lib/main.dart
class BlockWidget extends StatelessWidget {
final Block block;
const BlockWidget({
required this.block,
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(8),
child: switch (block) {
HeaderBlock(:final text) => Text(
text,
style: Theme.of(context).textTheme.displayMedium,
),
ParagraphBlock(:final text) => Text(text),
CheckboxBlock(:final text, :final isChecked) => Row(
children: [
Checkbox(value: isChecked, onChanged: (_) {}),
Text(text),
],
),
},
);
}
}
在第一個版本中,BlockWidget
,您切換了 Block
物件的欄位,藉此傳回 TextStyle
。現在,您可以切換 Block
物件本身的例項,並與代表該子類別的物件模式進行比對,並在過程中擷取物件的屬性。
由於您已將 Block
設為密封類別,因此 Dart 分析工具可檢查切換運算式中每個子類別是否進行處理。
另請注意,在這裡使用切換運算式,即可將結果直接傳遞至 child
元素,而不是先前需要的個別回傳敘述。
- 熱重新載入畫面,查看第一次轉譯的 JSON 資料核取方塊:
13. 恭喜
您已成功實驗不同的模式、記錄、強化開關和保護殼,以及密封類別。您之前提到了很多資訊,但還完全忽略了這些功能的面貌。如要進一步瞭解模式,請參閱功能規格。
不同的模式類型、不同的內容顯示情境,以及子模式的巢狀結構可能使行為看似「無限」。但顯而易見。
讓您想像以各種方式在 Flutter 中顯示內容,都可以運用圖案。使用模式可以安全地擷取資料,而且只需加入幾行程式碼即可建構 UI。
後續步驟
- 請參閱 Dart 說明文件的「語言」部分,瞭解模式、記錄、強化版切換和案例以及類別修飾符的說明文件。
參考文件
如需完整的程式碼範例,請參閱 flutter/codelabs
存放區。
如要瞭解各項新功能的詳細規格,請參閱原始設計文件: