1. Wprowadzenie
Dart 3 wprowadza wzorce do języka – to duża nowa kategoria gramatyki. Oprócz tego nowego sposobu pisania kodu DART jest jeszcze kilka ulepszeń językowych, w tym:
- rekordy do grupowania danych różnego typu,
- modyfikatory klas do kontrolowania dostępu;
- nowe wyrażenia Switch i instrukcje if-case.
Te funkcje zwiększają możliwości podczas pisania kodu Dart. Z tego ćwiczenia w Codelabs dowiesz się, jak używać ich do tworzenia bardziej kompaktowych, uproszczonych i elastycznych kodów.
W tym ćwiczeniu w programowaniu zakładamy, że masz pewną znajomość technologii Flutter i Dart. Jeśli nie wiesz, co zrobić, możesz odświeżyć podstawowe informacje, korzystając z tych materiałów:
Co utworzysz
To ćwiczenie w programie tworzy aplikację, która wyświetla dokument JSON w Flutter. Aplikacja symuluje dane JSON pochodzące ze źródła zewnętrznego. Plik JSON zawiera dane dokumentu, takie jak data modyfikacji, tytuł, nagłówki i akapity. Piszesz kod, który starannie pakuje dane w rekordy, dzięki czemu możesz je przenieść i rozpakować wszędzie tam, gdzie są potrzebne widżety Flutter.
Następnie na podstawie wzorców zbudujesz odpowiedni widżet, gdy wartość pasuje do tego wzorca. Dowiesz się też, jak używać wzorców do niszczenia danych w zmienne lokalne.
Czego się nauczysz
- Jak utworzyć rekord, który zawiera wiele wartości o różnych typach.
- Jak zwrócić wiele wartości z funkcji za pomocą rekordu.
- Jak używać wzorców do dopasowywania, walidacji i niszczenia danych z rekordów i innych obiektów.
- Łączenie wartości dopasowanych do wzorca z nowymi lub istniejącymi zmiennymi.
- Jak korzystać z nowych funkcji instrukcji Switch, wyrażeń Switch i instrukcji if-case.
- Dowiedz się, jak skorzystać ze sprawdzania kompletności, aby mieć pewność, że każde zgłoszenie jest obsługiwane w instrukcji przełączania lub wyrażeniu przełączania.
2. Konfigurowanie środowiska
- Zainstaluj pakiet SDK Flutter.
- Skonfiguruj edytor, np. Visual Studio Code (VS Code).
- Wykonaj czynności konfiguracji platformy dla co najmniej jednej platformy docelowej (iOS, Android, komputer lub przeglądarka).
3. Tworzenie projektu
Zanim zaczniesz poznawać wzorce, rekordy i inne nowe funkcje, poświęć chwilę na utworzenie prostego projektu Flutter, w ramach którego napiszesz cały kod.
Tworzenie projektu Flutter
- Użyj polecenia
flutter create
, aby utworzyć nowy projekt o nazwiepatterns_codelab
. Flaga--empty
uniemożliwia utworzenie w plikulib/main.dart
standardowej aplikacji licznika, którą i tak należy usunąć.
flutter create --empty patterns_codelab
- Następnie otwórz katalog
patterns_codelab
przy użyciu VS Code.
code patterns_codelab
Ustawianie minimalnej wersji pakietu SDK
- Ustaw ograniczenie wersji pakietu SDK w projekcie, aby zależeć od aplikacji Dart 3 lub nowszej.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Konfigurowanie projektu
W tym kroku utworzysz lub zaktualizujesz 2 pliki Dart:
- plik
main.dart
, który zawiera widżety aplikacji; - Plik
data.dart
zawierający dane aplikacji.
W kolejnych krokach będziesz modyfikować oba te pliki.
Zdefiniuj dane aplikacji
- Utwórz nowy plik
lib/data.dart
i dodaj do niego ten kod:
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"
}
]
}
''';
Wyobraź sobie, że program odbiera dane ze źródła zewnętrznego, np. strumień wejścia-wyjścia lub żądanie HTTP. Dzięki temu ćwiczeniu w programowaniu upraszczasz bardziej realistyczny przypadek użycia, naśmiewając się z przychodzących danych JSON za pomocą wielowierszowego ciągu w zmiennej documentJson
.
Dane JSON są zdefiniowane w klasie Document
. W dalszej części tego ćwiczenia w programie dodasz funkcje, które zwracają dane z przeanalizowanego pliku JSON. Ta klasa definiuje i inicjuje pole _json
w swoim konstruktorze.
Uruchom aplikację
Polecenie flutter create
tworzy plik lib/main.dart
w domyślnej strukturze plików Flutter.
- Aby utworzyć punkt początkowy aplikacji, zastąp zawartość pola
main.dart
tym kodem:
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'),
),
],
),
);
}
}
Dodałeś do aplikacji następujące dwa widżety:
DocumentApp
konfiguruje najnowszą wersję Material Design do obsługi motywów interfejsu.DocumentScreen
udostępnia układ wizualny strony za pomocą widżetuScaffold
.
- Aby upewnić się, że wszystko działa prawidłowo, uruchom aplikację na hoście, klikając Uruchom i debuguj:
- Domyślnie Flutter wybiera dostępną platformę docelową. Aby zmienić platformę docelową, wybierz bieżącą platformę na pasku stanu:
Powinna pojawić się pusta ramka z elementami title
i body
zdefiniowanymi w widżecie DocumentScreen
:
5. Tworzenie i zwracanie rekordów
W tym kroku użyjesz rekordów do zwrócenia wielu wartości z wywołania funkcji. Następnie wywołasz tę funkcję w widżecie DocumentScreen
, aby uzyskać dostęp do wartości i odzwierciedlić je w interfejsie.
Tworzenie i zwracanie rekordu
- W obiekcie
data.dart
dodaj nową metodę pobierania do klasy Document o nazwiemetadata
, która zwraca rekord:
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.
}
Zwracany typ tej funkcji to rekord z dwoma polami, jedno o typie String
, a drugie typu DateTime
.
Instrukcja zwrotna tworzy nowy rekord przez umieszczenie 2 wartości w nawiasach: (title, modified: now)
.
Pierwsze pole ma nazwę i nie ma nazwy, a drugie ma nazwę modified
.
Dostęp do pól rekordu
- W widżecie
DocumentScreen
wywołaj metodę pobieraniametadata
w metodziebuild
, co pozwoli Ci uzyskać rekord i uzyskać dostęp do jego wartości:
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.
),
),
],
),
);
}
}
Metoda pobierania metadata
zwraca rekord, który jest przypisany do lokalnej zmiennej metadataRecord
. Rekordy to łatwy i łatwy sposób na zwrócenie wielu wartości z pojedynczego wywołania funkcji i przypisanie ich do zmiennej.
Aby uzyskać dostęp do poszczególnych pól utworzonych w tym rekordzie, możesz użyć polecenia wbudowanej składni metody pobierania.
- Aby uzyskać pole pozycjonowania (pole bez nazwy, np.
title
), użyj w rekordzie metody pobierania$<num>
. Zwracane są tylko pola bez nazwy. - Pola nazwane, takie jak
modified
, nie mają metody pobierania pozycjonowania, więc możesz użyć jej nazwy bezpośrednio, np.metadataRecord.modified
.
Aby określić nazwę metody pobierania dla pola pozycji, zacznij od $1
i pomiń pola nazwane. Na przykład:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Załaduj ponownie, aby zobaczyć wartości JSON wyświetlane w aplikacji. Wtyczka VS Code Dart ładuje się ponownie przy każdym zapisywaniu pliku.
Jak widać, każde pole zachowało swój typ.
- Metoda
Text()
przyjmuje ciąg znaków jako pierwszy argument. - Pole
modified
zawiera typ DataTime i jest konwertowane na formatString
przy użyciu interpolacji ciągów znaków.
Innym bezpiecznym typem zwracania różnych typów danych jest zdefiniowanie klasy, która jest bardziej szczegółowy.
6. Dopasowywanie i niszczenie na podstawie wzorców
Rejestry mogą skutecznie zbierać różne typy danych i łatwo je przekazywać. Teraz możesz ulepszyć kod, używając wzorców.
Wzorzec reprezentuje strukturę, którą może przyjmować jedna lub więcej wartości, na przykład plan. Wzorce porównują wartości z rzeczywistymi, aby ustalić, czy są pasujące.
Niektóre pasujące wzorce niszczą pasującą wartość, pobierając z niej dane. Usuwanie struktury pozwala rozpakować wartości z obiektu, aby przypisać je do zmiennych lokalnych lub przeprowadzić w nich dalsze dopasowywanie.
Niszczenie rekordu na zmienne lokalne
- Refaktoryzacja metody
build
metodyDocumentScreen
w celu wywołania metodymetadata
i użycie jej do zainicjowania deklaracji zmiennej wzorca:
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
),
),
],
),
);
}
}
Wzorzec rekordu (title, modified: modified)
zawiera dwa wzorce zmiennych, które pasują do pól rekordu zwróconego przez funkcję metadata
.
- Wyrażenie pasuje do wzorca podrzędnego, ponieważ wynikiem jest rekord z 2 polami, z których jedno o nazwie
modified
. - Ponieważ są one dopasowane, wzorzec deklaracji zmiennej niszczy wyrażenie, uzyskuje dostęp do jego wartości i wiąże je z nowymi zmiennymi lokalnymi tego samego typu i nazw
String title
iDateTime modified
.
W skrócie można powiedzieć, że nazwa pola i wypełniająca ją zmienna są takie same. Przeprowadź refaktoryzację metody build
funkcji DocumentScreen
w następujący sposób.
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',
),
),
],
),
);
}
}
Składnia wzorca zmiennej :modified
jest skrótem od terminu modified: modified
. Jeśli potrzebujesz nowej zmiennej lokalnej o innej nazwie, możesz zamiast niej wpisać modified: localModified
.
- Załaduj ponownie, aby uzyskać taki sam efekt jak w poprzednim kroku. Działanie jest dokładnie takie samo. Teraz Twój kod jest bardziej zwięzły.
7. Użyj wzorców do wyodrębniania danych
W niektórych kontekstach wzory nie tylko dopasowują i niszczą, ale mogą też podejmować decyzję o tym, jak działa kod, w zależności od tego, czy dany wzorzec pasuje czy nie. Są to tak zwane wzorce możliwe do omówienia.
Wzorzec deklaracji zmiennej użyty w ostatnim kroku jest nierozstrzygniętym wzorcem: wartość musi być zgodna ze wzorcem. W przeciwnym razie jest to błąd i nie da się zniszczyć. pomyśl o deklaracji lub przypisaniu zmiennej; nie możesz przypisać wartości do zmiennej, jeśli są różnych typów.
Z kolei wzory z możliwością powtarzania są używane w kontekstach przepływu kontroli:
- Oczekują, że niektóre wartości, z którymi są porównywane, nie będą się zgadzać.
- Mają one wpływać na przepływ kontrolny w zależności od tego, czy wartość się zgadza.
- Jeśli nie są zgodne, nie przerywają wykonania i wyświetlają błąd, tylko przechodzą do następnej instrukcji.
- Mogą niszczyć i powiązać zmienne, których można używać tylko wtedy, gdy są dopasowane
Odczytywanie wartości JSON bez wzorców
W tej sekcji zapoznasz się z danymi bez dopasowywania do wzorca, aby dowiedzieć się, jak wzorce mogą Ci pomóc w pracy z danymi JSON.
- Zastąp poprzednią wersję tabeli
metadata
wersją, która odczytuje wartości z mapy_json
. Skopiuj i wklej tę wersjęmetadata
do klasyDocument
:
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.
}
}
Ten kod sprawdza, czy dane mają prawidłową strukturę bez użycia wzorców. W późniejszym kroku użyjesz dopasowywania do wzorca, by przeprowadzić tę samą weryfikację z wykorzystaniem mniejszej liczby kodów. Przed wykonaniem innych działań sprawdza 3 testy:
- Plik JSON zawiera oczekiwaną strukturę danych:
if (_json.containsKey('metadata'))
- Dane mają oczekiwany typ:
if (metadataJson is Map)
- Potwierdzenie, że dane nie mają wartości null, co zostało domyślnie potwierdzone podczas poprzedniego sprawdzania.
Odczytywanie wartości JSON za pomocą wzorca mapy
Dzięki niemu możesz sprawdzić, czy plik JSON ma oczekiwaną strukturę, korzystając z wzorca mapy.
- Zastąp poprzednią wersję pliku
metadata
tym kodem:
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.
}
}
Oto nowy rodzaj wyrażenia „if-case” (zaprezentowane w Dart 3) – if-case. Treść zgłoszenia jest wykonywana tylko wtedy, gdy wzorzec przypadku pasuje do danych w polu _json
. To dopasowanie przechodzi te same testy, które zostały przez Ciebie wypisane w pierwszej wersji metadata
, aby zweryfikować przychodzącego kodu JSON. Ten kod weryfikuje te informacje:
_json
to typ mapy._json
zawiera kluczmetadata
._json
nie ma wartości null._json['metadata']
to również typ mapy._json['metadata']
zawiera kluczetitle
imodified
.title
ilocalModified
są ciągami tekstowymi i nie mają wartości null.
Jeśli wartość jest niezgodna, wzorzec odrzuca żądania (odmawia kontynuacji wykonywania) i przechodzi do klauzuli else
. Jeśli dopasowanie się powiedzie, wzorzec niszczy wartości title
i modified
z mapy oraz wiąże je z nowymi zmiennymi lokalnymi.
Pełną listę wzorców znajdziesz w tabeli w sekcji Wzorce w specyfikacji funkcji.
8. Przygotowanie aplikacji na więcej wzorców
Na razie zajmujesz się częścią metadata
danych JSON. W tym kroku doprecyzujesz logikę biznesową, aby przetwarzać dane na liście blocks
i renderować je w Twojej aplikacji.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
Tworzenie klasy, w której są przechowywane dane
- Dodaj do
data.dart
nową klasęBlock
, która będzie używana do odczytywania i przechowywania danych z jednego z bloków w danych 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');
}
}
}
Konstruktor fabryczny fromJson()
używa tego samego wzoru typu „if-case” z wzorcem mapy, który widzieli wcześniej.
Zauważ, że json
pasuje do wzorca mapy, mimo że jeden z klawiszy (checked
) nie został uwzględniony we wzorcu. Wzorce mapy ignorują wszystkie wpisy w obiekcie mapy, które nie zostały wyraźnie uwzględnione we wzorcu.
Zwraca listę obiektów Block
- Następnie dodaj nową funkcję (
getBlocks()
) do klasyDocument
.getBlocks()
analizuje plik JSON do postaci instancji klasyBlock
i zwraca listę bloków do wyrenderowania w interfejsie użytkownika:
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.
}
Funkcja getBlocks()
zwraca listę obiektów Block
, których użyjesz później do utworzenia interfejsu użytkownika. Znajome instrukcje typu if-case przeprowadza weryfikację i przekazuje wartość metadanych blocks
do nowego elementu List
o nazwie blocksJson
(bez wzorców, do rzutowania potrzebna jest metoda toList()
).
Literał listy zawiera kolekcję dla w celu wypełnienia nowej listy obiektami Block
.
W tej sekcji nie omawiamy żadnych funkcji związanych ze wzorcami, których nie wypróbowałeś(-aś) jeszcze w ramach tego ćwiczenia. W następnym kroku przygotujesz elementy listy do renderowania w interfejsie użytkownika.
9. Użyj wzorców do wyświetlania dokumentu
Udało Ci się zniszczyć i ponownie skomponować dane JSON, używając instrukcji if-case i wzorców możliwych do obalania. Istnieją jednak tylko 1 z ulepszeń konstrukcji przepływu, które zawierają wzorce. Teraz wykorzystujesz swoją wiedzę o podważanych wzorcach, by zmieniać stwierdzenia.
Kontroluj, co jest renderowane na podstawie wzorców, za pomocą instrukcji Switch
- W narzędziu
main.dart
utwórz nowy widżet (BlockWidget
), który określa styl każdego bloku na podstawie jego polatype
.
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,
),
);
}
}
Instrukcja Switch w metodzie build
przełącza się na pole type
obiektu block
.
- W pierwszej instrukcji przypadku używa się wzorca ciągów stałych. Wzorzec jest dopasowywany, jeśli
block.type
jest równa stałej wartościh1
. - Druga instrukcja oparta na ciągach znaków korzysta z wzorca logicznego lub wzorca z 2 wzorcami ciągów stałych jako schematów podrzędnych. Wzorzec jest dopasowywany, jeśli
block.type
pasuje do jednego z podwzorcówp
lubcheckbox
.
- Ostatni przykład to wzorzec wieloznaczny:
_
. Symbole wieloznaczne w przypadkach przełączania pasują do wszystkich pozostałych. Działają tak samo jak klauzuledefault
, które nadal są dozwolone w instrukcjach Switch (są tylko trochę bardziej szczegółowe).
Wzorce z symbolem wieloznacznym mogą być używane wszędzie tam, gdzie jest dozwolony wzorzec – na przykład we wzorcu deklaracji zmiennej: var (title, _) = document.metadata;
W tym kontekście symbol wieloznaczny nie tworzy powiązania żadnej zmiennej. Odrzuca drugie pole.
W następnej sekcji dowiesz się więcej o funkcjach przełączania po wyświetleniu obiektów Block
.
Wyświetlanie treści dokumentu
Utwórz zmienną lokalną zawierającą listę obiektów Block
, wywołując metodę getBlocks()
w metodzie build
widżetu DocumentScreen
.
- Zastąp istniejącą metodę
build
wDocumentationScreen
tą wersją:
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.
],
),
);
}
}
Wiersz BlockWidget(block: blocks[index])
tworzy widżet BlockWidget
dla każdego elementu na liście bloków zwróconych przez metodę getBlocks()
.
- Po uruchomieniu aplikacji na ekranie powinny pojawić się bloki:
10. Używanie wyrażeń Switch
Wzorce zapewniają wiele możliwości w funkcjach switch
i case
. Aby można było z nich korzystać w większej liczbie miejsc, Dart ma wyrażenia przełączające. Seria przypadków może zawierać wartość bezpośrednio do przypisania zmiennej lub instrukcji zwrotnej.
Konwertuj instrukcję Switch na wyrażenie Switch
Analizator DART udostępnia pomoce, które ułatwiają wprowadzanie zmian w kodzie.
- Przesuń kursor do instrukcji Switch z poprzedniej sekcji.
- Kliknij żarówkę, aby zobaczyć dostępne podpowiedzi.
- Wybierz asystenta Konwertuj na inne wyrażenie.
Nowa wersja tego kodu wygląda tak:
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,
),
);
}
}
Wyrażenie Switch wygląda podobnie do instrukcji Switch, jednak eliminuje słowo kluczowe case
i wykorzystuje =>
do oddzielania wzorca od jego treści. W przeciwieństwie do instrukcji Switch wyrażenia Switch zwracają wartość i mogą być używane wszędzie tam, gdzie można użyć wyrażenia.
11. Użyj wzorców obiektów
Dart jest językiem zorientowanym na obiekty, więc wzory odnoszą się do wszystkich obiektów. W tym kroku włączysz wzorzec obiektu i niszczysz właściwości obiektów, aby ulepszyć logikę renderowania daty w interfejsie.
Wyodrębnianie właściwości z wzorców obiektów
W tej sekcji możesz ulepszyć sposób wyświetlania daty ostatniej modyfikacji za pomocą wzorców.
- Dodaj metodę
formatDate
do metodymain.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',
};
}
Ta metoda zwraca wyrażenie Switch, które przełącza się na wartość difference
(obiekt Duration
). Reprezentuje przedział czasu między today
a wartością modified
z danych JSON.
W każdym przypadku wyrażenia Switch jest używany wzorzec obiektu, który dopasowuje się przez wywołanie metody pobierania we właściwościach inDays
i isNegative
obiektu. Wygląda na to, że składnia powoduje utworzenie obiektu Duration, ale w rzeczywistości uzyskuje dostęp do pól obiektu difference
.
W pierwszych 3 przypadkach używane są stałe podwzorce 0
, 1
i -1
, aby dopasować właściwość obiektu inDays
i zwrócić odpowiedni ciąg.
W 2 ostatnich przypadkach czas trwania jest dłuższy niż dzisiaj, wczoraj i jutro:
- Jeśli właściwość
isNegative
pasuje do stałego wzorca logicznegotrue
, co oznacza, że data modyfikacji przypada w przeszłości, jest wyświetlana dni temu. - Jeśli w tym przypadku nie ma różnicy, czas trwania musi być dodatnią liczbą dni (nie trzeba weryfikować własności za pomocą funkcji
isNegative: false
), więc data modyfikacji przypada w przyszłości i zawiera wartość dni od teraz.
Dodaj logikę formatowania tygodniową
- Dodaj do funkcji formatowania 2 nowe przypadki, aby wskazać czasy trwania dłuższe niż 7 dni i wyświetlać je w interfejsie jako tygodnie:
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',
};
}
Ten kod wprowadza klauzule zabezpieczające:
- W klauzuli zabezpieczającej używane jest słowo kluczowe
when
po wzorcu wielkości liter. - Można ich używać w przypadkach if-case, instrukcjach Switch i wyrażeniach Switch.
- Dodają warunek do wzorca dopiero po jego dopasowaniu.
- Jeśli klauzula zabezpieczająca zwróci wartość fałsz, cały wzorzec zostanie odrzucony, a wykonanie przechodzi do kolejnego przypadku.
Dodaj nowo sformatowaną datę do interfejsu użytkownika
- Na koniec zaktualizuj metodę
build
w tabeliDocumentScreen
, aby używała funkcjiformatDate
:
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]);
},
),
),
],
),
);
}
}
- Załaduj ponownie, aby zobaczyć zmiany w aplikacji:
12. Zabezpiecz klasę, aby przeprowadzić kompleksowe przełączanie
Zwróć uwagę, że na końcu ostatniego przełącznika nie został użyty symbol wieloznaczny ani domyślna wielkość liter. Pomimo tego, że zawsze warto uwzględniać przypadki przypadkiem niezgodności z zasadami, nie wystarczy jednak użyć prostego przykładu takiego jak ten, bo wiesz, że zdefiniowane przez Ciebie przypadki uwzględniają wszystkie możliwe wartości inDays
.
Obsługa każdego przypadku przełączenia jest nazywana pełnym przełącznikiem. Na przykład włączenie typu bool
jest wyczerpujące, jeśli występują przypadki tych atrybutów: true
i false
. Przełączenie typu enum
jest możliwe tylko wtedy, gdy występują przypadki dla każdej wartości wyliczenia, ponieważ wyliczenia reprezentują stałą liczbę stałych wartości.
Dart 3 rozszerzył sprawdzanie kompletności obiektów i hierarchii klas za pomocą nowego modyfikatora klas sealed
. Refaktoryzacja klasy Block
jako zapieczętowanej klasy nadrzędnej.
Tworzenie podklas
- W usłudze
data.dart
utwórz 3 nowe zajęcia (HeaderBlock
,ParagraphBlock
iCheckboxBlock
), które obejmują okresBlock
:
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);
}
Każda z tych klas odpowiada różnym wartościom type
z pierwotnego kodu JSON: 'h1'
, 'p'
i 'checkbox'
.
Przypieczętuj superklasę
- Oznacz zajęcia
Block
jakosealed
. Następnie zinterpretuj przypadek „if-case” jako wyrażenie Switch, które zwraca podklasę odpowiadającą funkcjitype
określonej w pliku 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'),
};
}
}
Słowo kluczowe sealed
jest modyfikatorem klasy, co oznacza, że możesz rozszerzać lub zaimplementować tę klasę tylko w tej samej bibliotece. Analizator zna podtypy tej klasy, więc jeśli przełącznik nie obejmie jednego z nich i nie będzie kompletny.
Aby wyświetlić widżety, użyj wyrażenia przełącznika
- Zaktualizuj klasę BlockWidget w
main.dart
przy użyciu wyrażenia Switch, które w każdym przypadku korzysta ze wzorców obiektów:
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),
],
),
},
);
}
}
W pierwszej wersji obiektu BlockWidget
włączono pole obiektu Block
tak, aby zwracało ono TextStyle
. Teraz przełączasz instancję samego obiektu Block
i dopasowujesz go do wzorców obiektów, które reprezentują jego podklasy, wyodrębniając w tym procesie właściwości obiektu.
Analizator Dart może sprawdzić, czy każda podklasa jest obsługiwana w wyrażeniu Switch, ponieważ Block
jest klasą zapieczętowaną.
Zwróć też uwagę na to, że użycie w tym miejscu wyrażenia przełącznika pozwala przekazać wynik bezpośrednio do elementu child
, a nie w osobnej instrukcji zwrotnej wymaganej wcześniej.
- Załaduj ponownie, aby zobaczyć pole wyboru danych JSON wyrenderowanych po raz pierwszy:
13. Gratulacje
Udało Ci się przetestować wzorce, rekordy, rozszerzone przełączniki, wielkość liter i separatory zamknięte. Omówiono mnóstwo informacji, ale brakuje w nim żadnej części informacji. Więcej informacji o wzorcach znajdziesz w specyfikacji funkcji.
Różne typy wzorców, różne konteksty, w których mogą się pojawiać, oraz potencjalne zagnieżdżanie podwzorców sprawiają, że możliwości działania wydają się nieograniczone. Są jednak dobrze widoczne.
Korzystając z wzorców, możesz używać różnych sposobów wyświetlania treści w Flutter. Korzystając z wzorców, możesz bezpiecznie wyodrębniać dane i tworzyć interfejs za pomocą kilku wierszy kodu.
Co dalej?
- Zapoznaj się z dokumentacją dotyczącą wzorców, rekordów, ulepszonych przełączników i przypadków oraz modyfikatorów klas w sekcji Język dokumentacji Dart.
Dokumentacja
Pełny przykładowy kod znajdziesz krok po kroku w repozytorium flutter/codelabs
.
Szczegółowe specyfikacje wszystkich nowych funkcji znajdziesz w dokumentacji oryginalnej projektowania: