1. Введение
Dart 3 вводит в язык шаблоны — новую важную категорию грамматики. Помимо этого нового способа написания кода Dart, существует несколько других усовершенствований языка, в том числе
- записи для объединения данных разных типов,
- модификаторы класса для управления доступом и
- новые выражения переключения и операторы if-case .
Эти функции расширяют возможности выбора при написании кода Dart. В этой лаборатории вы узнаете, как использовать их, чтобы сделать код более компактным, оптимизированным и гибким.
В этой лаборатории предполагается, что вы знакомы с Flutter и Dart. Если вы чувствуете, что немного заржавели, попробуйте освежить основы с помощью следующих ресурсов:
Что ты построишь
Эта лаборатория кода создает приложение, которое отображает документ JSON во Flutter. Приложение имитирует JSON, поступающий из внешнего источника. JSON содержит данные документа, такие как дата изменения, заголовок, заголовки и абзацы. Вы пишете код для аккуратной упаковки данных в записи, чтобы их можно было перенести и распаковать там, где это необходимо вашим виджетам Flutter.
Затем вы используете шаблоны для создания соответствующего виджета, когда значение соответствует этому шаблону. Вы также увидите, как использовать шаблоны для деструктуризации данных в локальные переменные.
Что вы узнаете
- Как создать запись, в которой хранится несколько значений разных типов.
- Как вернуть несколько значений из функции с помощью записи.
- Как использовать шаблоны для сопоставления, проверки и деструктуризации данных из записей и других объектов.
- Как привязать значения, соответствующие шаблону, к новым или существующим переменным.
- Как использовать новые возможности операторов переключения, выражений переключения и операторов if-case.
- Как воспользоваться преимуществами проверки полноты, чтобы гарантировать, что каждый случай обрабатывается в операторе переключения или выражении переключения.
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
- Затем откройте каталог
patterns_codelab
с помощью VS Code.
code patterns_codelab
Установите минимальную версию SDK
- Установите ограничение версии SDK для вашего проекта в зависимости от Dart 3 или более поздней версии.
pubspec.yaml
environment:
sdk: ^3.0.0
4. Настройте проект
На этом этапе вы создаете или обновляете два файла Dart:
- Файл
main.dart
, содержащий виджеты для приложения, и - Файл
data.dart
, содержащий данные приложения.
Вы продолжите изменять оба этих файла на последующих шагах.
Определите данные для приложения
- Создайте новый файл
lib/data.dart
и добавьте в него следующий код:
библиотека/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"
}
]
}
''';
Представьте себе программу, получающую данные из внешнего источника, например потока ввода-вывода или HTTP-запроса. В этой кодовой лаборатории вы упростите этот более реалистичный вариант использования, имитируя входящие данные JSON с помощью многострочной строки в переменной documentJson
.
Данные JSON определены в классе Document
. Позже в этой лаборатории кода вы добавите функции, которые возвращают данные из проанализированного JSON. Этот класс определяет и инициализирует поле _json
в своем конструкторе.
Запустите приложение
Команда flutter create
создает файл lib/main.dart
как часть файловой структуры Flutter по умолчанию.
- Чтобы создать отправную точку для приложения, замените содержимое
main.dart
следующим кодом:
библиотека/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 для оформления пользовательского интерфейса. -
DocumentScreen
обеспечивает визуальный макет страницы с помощью виджетаScaffold
.
- Чтобы убедиться, что все работает гладко, запустите приложение на своем хост-компьютере, нажав «Запустить и отладить» :
- По умолчанию Flutter выбирает любую доступную целевую платформу. Чтобы изменить целевую платформу, выберите текущую платформу в строке состояния:
Вы должны увидеть пустой фрейм с элементами title
и body
, определенными в виджете DocumentScreen
:
5. Создание и возврат записей
На этом этапе вы используете записи для возврата нескольких значений из вызова функции. Затем вы вызываете эту функцию в виджете DocumentScreen
, чтобы получить доступ к значениям и отобразить их в пользовательском интерфейсе.
Создать и вернуть запись
- В
data.dart
добавьте в класс Document новый метод получения с именемmetadata
, который возвращает запись:
библиотека/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
.
Оператор return создает новую запись, заключая два значения в круглые скобки (title, modified: now)
.
Первое поле является позиционным и безымянным, а второе поле называется modified
.
Доступ к полям записи
- В виджете
DocumentScreen
вызовите метод полученияmetadata
в методеbuild
, чтобы вы могли получить свою запись и получить доступ к ее значениям:
библиотека/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
возвращает запись, которая присваивается локальной переменной metadataRecord
. Записи — это легкий и простой способ вернуть несколько значений из одного вызова функции и назначить их переменной.
Чтобы получить доступ к отдельным полям, содержащимся в этой записи, вы можете использовать встроенный синтаксис метода получения записей.
- Чтобы получить позиционное поле (поле без имени, например
title
), используйте метод получения$<num>
для записи. Это возвращает только безымянные поля. - Именованные поля, такие как
modified
не имеют позиционного метода получения, поэтому вы можете использовать его имя напрямую, например,metadataRecord.modified
.
Чтобы определить имя метода получения для позиционного поля, начните с $1
и пропускайте именованные поля. Например:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- Горячая перезагрузка, чтобы увидеть значения JSON, отображаемые в приложении. Плагин VS Code Dart выполняет горячую перезагрузку каждый раз, когда вы сохраняете файл.
Вы можете видеть, что каждое поле фактически сохранило свой тип.
- Метод
Text()
принимает строку в качестве первого аргумента. -
modified
поле представляет собой DateTime и преобразуется вString
с помощью интерполяции строк .
Другой типобезопасный способ возврата различных типов данных — определение класса, который является более подробным.
6. Сопоставление и деструктуризация с помощью шаблонов
Записи могут эффективно собирать различные типы данных и легко передавать их. Теперь улучшите свой код, используя шаблоны .
Шаблон представляет собой структуру, которая может принимать одно или несколько значений, например схему. Шаблоны сравниваются с фактическими значениями, чтобы определить, совпадают ли они.
Некоторые шаблоны при совпадении деструктурируют совпадающее значение, извлекая из него данные. Деструктуризация позволяет распаковывать значения из объекта, чтобы присвоить их локальным переменным, или выполнить их дальнейшее сопоставление.
Разбить запись на локальные переменные
- Рефакторинг метода
build
DocumentScreen
для вызоваmetadata
и использования его для инициализации объявления переменной шаблона :
библиотека/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
.
Существует сокращение, обозначающее, что имя поля и заполняющая его переменная совпадают. Выполните рефакторинг метода build
DocumentScreen
следующим образом.
библиотека/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
:
библиотека/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)
- Данные не равны нулю , что неявно подтверждено в предыдущей проверке.
Чтение значений JSON с использованием шаблона карты
Используя опровержимый шаблон, вы можете проверить, что JSON имеет ожидаемую структуру, используя шаблон карты .
- Замените предыдущую версию
metadata
этим кодом:
библиотека/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
являются строками и не имеют значения NULL.
Если значение не совпадает, шаблон отвергается (отказывается продолжать выполнение) и переходит к предложению else
. Если сопоставление прошло успешно, шаблон деструктурирует значения title
и modified
из карты и привязывает их к новым локальным переменным.
Полный список шаблонов см. в таблице в разделе «Шаблоны» спецификации функции.
8. Подготовьте приложение для дополнительных шаблонов.
До сих пор вы обращались к части metadata
данных JSON. На этом этапе вы еще немного усовершенствуете свою бизнес-логику, чтобы обрабатывать данные в списке blocks
и отображать их в своем приложении.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
Создайте класс, который хранит данные
- Добавьте новый класс
Block
вdata.dart
, который используется для чтения и хранения данных для одного из блоков данных JSON.
библиотека/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 с шаблоном карты, который вы видели раньше.
Обратите внимание, что json
соответствует шаблону карты, хотя один из ключей, checked
, не учитывается в шаблоне. Шаблоны карт игнорируют любые записи в объекте карты, которые явно не учтены в шаблоне.
Вернуть список объектов Block
- Затем добавьте новую функцию
getBlocks()
в классDocument
.getBlocks()
анализирует JSON на экземпляры классаBlock
и возвращает список блоков для отображения в вашем пользовательском интерфейсе:
библиотека/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
, который вы позже используете для создания пользовательского интерфейса. Знакомый оператор if-case выполняет проверку и преобразует значение метаданных blocks
в новый List
с blocksJson
(без шаблонов для приведения потребуется метод toList()
).
Литерал списка содержит коллекцию для заполнения нового списка объектами Block
.
В этом разделе не представлены какие-либо функции, связанные с шаблонами, которые вы еще не опробовали в этой лаборатории кода. На следующем шаге вы подготовитесь к отображению элементов списка в пользовательском интерфейсе.
9. Используйте шаблоны для отображения документа
Теперь вы успешно деструктурируете и перекомпонуете свои данные JSON, используя оператор if-case и опровержимые шаблоны. Но if-case — это лишь одно из усовершенствований управления структурами потока, которые идут в комплекте с шаблонами. Теперь вы примените свои знания об опровержимых закономерностях для переключения утверждений.
Управляйте тем, что отображается, используя шаблоны с операторами переключения.
- В
main.dart
создайте новый виджетBlockWidget
, который определяет стиль каждого блока на основе его поляtype
.
библиотека/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
объекта.
- Первый оператор case использует шаблон постоянной строки . Шаблон соответствует, если
block.type
равен постоянному значениюh1
. - Второй оператор case использует шаблон «логическое ИЛИ» с двумя шаблонами константных строк в качестве подшаблонов. Шаблон соответствует, если
block.type
соответствует любому из подшаблоновp
илиcheckbox
.
- Последний случай — это шаблон подстановочных знаков
_
. Подстановочные знаки в случаях переключателей соответствуют всему остальному. Они ведут себя так же, как и предложенияdefault
, которые по-прежнему разрешены в операторах переключения (только они немного более многословны).
Шаблоны подстановочных знаков можно использовать везде, где это разрешено, например в шаблоне объявления переменных: var (title, _) = document.metadata;
В этом контексте подстановочный знак не привязывает никакую переменную. Он отбрасывает второе поле.
В следующем разделе вы узнаете о дополнительных функциях переключения после отображения объектов Block
.
Отображение содержимого документа
Создайте локальную переменную, содержащую список объектов Block
, вызвав getBlocks()
в методе build
виджета DocumentScreen
.
- Замените существующий метод
build
вDocumentationScreen
этой версией:
библиотека/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 предоставляет помощь , которая поможет вам внести изменения в код.
- Переместите курсор на оператор переключения из предыдущего раздела.
- Нажмите на лампочку, чтобы просмотреть доступные ассисты.
- Выберите « Преобразовать для переключения выражений» .
Новая версия этого кода выглядит так:
библиотека/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
и используется =>
для отделения шаблона от тела дела. В отличие от операторов переключения, выражения переключения возвращают значение и могут использоваться везде, где можно использовать выражение.
11. Используйте шаблоны объектов
Dart — объектно-ориентированный язык, поэтому шаблоны применимы ко всем объектам. На этом этапе вы включаете шаблон объекта и деструктурируете свойства объекта, чтобы улучшить логику рендеринга даты в вашем пользовательском интерфейсе.
Извлечение свойств из шаблонов объектов
В этом разделе вы улучшите способ отображения даты последнего изменения с помощью шаблонов.
- Добавьте метод
formatDate
вmain.dart
:
библиотека/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.
В каждом случае выражения переключения используется шаблон объекта, который соответствует путем вызова геттеров свойств объекта inDays
и isNegative
. Синтаксис выглядит так, как будто он создает объект Duration, но на самом деле он обращается к полям объекта difference
.
В первых трех случаях используются константные подшаблоны 0
, 1
и -1
для сопоставления свойства объекта inDays
и возврата соответствующей строки.
Последние два случая обрабатывают продолжительность за пределами сегодняшнего, вчерашнего и завтрашнего дня:
- Если свойство
isNegative
соответствует шаблону логической константыtrue
, что означает, что дата изменения была в прошлом, оно отображается дней назад . - Если в этом случае разница не уловлена, то продолжительность должна быть положительным числом дней (нет необходимости явно проверять с помощью
isNegative: false
), поэтому дата изменения находится в будущем и отображает дни, прошедшие с настоящего момента .
Добавьте логику форматирования на несколько недель
- Добавьте два новых случая в функцию форматирования, чтобы определить продолжительность более 7 дней, чтобы пользовательский интерфейс мог отображать их в виде недель :
библиотека/main.dart
String formatDate(DateTime dateTime) {
final today = DateTime.now();
final difference = dateTime.difference(today);
return switch (difference) {
Duration(inDays: 0) => 'today',
Duration(inDays: 1) => 'tomorrow',
Duration(inDays: -1) => 'yesterday',
Duration(inDays: final days) when days > 7 => '${days ~/ 7} weeks from now', // Add from here
Duration(inDays: final days) when days < -7 =>
'${days.abs() ~/ 7} weeks ago', // to here.
Duration(inDays: final days, isNegative: true) => '${days.abs()} days ago',
Duration(inDays: final days) => '$days days from now',
};
}
Этот код вводит защитные положения :
- Предложение Guard использует ключевое слово
when
после шаблона регистра. - Их можно использовать в if-cases, операторах переключения и выражениях переключения.
- Они добавляют условие к шаблону только после его сопоставления .
- Если предложение Guard оценивается как ложное, весь шаблон опровергается , и выполнение переходит к следующему случаю.
Добавьте новую отформатированную дату в пользовательский интерфейс.
- Наконец, обновите метод
build
вDocumentScreen
, чтобы использовать функциюformatDate
:
библиотека/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
:
библиотека/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:
библиотека/data.dart
sealed class Block {
Block();
factory Block.fromJson(Map<String, Object?> json) {
return switch (json) {
{'type': 'h1', 'text': String text} => HeaderBlock(text),
{'type': 'p', 'text': String text} => ParagraphBlock(text),
{'type': 'checkbox', 'text': String text, 'checked': bool checked} =>
CheckboxBlock(text, checked),
_ => throw const FormatException('Unexpected JSON format'),
};
}
}
Ключевое слово sealed
— это модификатор класса , который означает, что вы можете расширить или реализовать этот класс только в той же библиотеке . Поскольку анализатору известны подтипы этого класса, он сообщает об ошибке, если переключатель не охватывает один из них и не является исчерпывающим.
Используйте выражение переключателя для отображения виджетов
- Обновите класс BlockWidget в
main.dart
, добавив выражение переключателя, которое использует шаблоны объектов для каждого случая:
библиотека/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
элементу, в отличие от необходимого ранее отдельного оператора возврата.
- Горячая перезагрузка, чтобы увидеть данные JSON флажка, отображаемые в первый раз:
13. Поздравления
Вы успешно экспериментировали с шаблонами, записями, расширенными переключателями и регистрами, а также закрытыми классами. Вы рассмотрели много информации, но лишь слегка коснулись этих функций. Дополнительную информацию о шаблонах см. в спецификации функции .
Различные типы паттернов, разные контексты, в которых они могут появляться, а также потенциальное вложение подпаттернов делают возможности поведения, казалось бы, безграничными . Но их легко увидеть.
Вы можете представить себе всевозможные способы отображения контента во Flutter с использованием шаблонов. Используя шаблоны, вы можете безопасно извлекать данные, чтобы построить свой пользовательский интерфейс в несколько строк кода.
Что дальше?
- Ознакомьтесь с документацией по шаблонам, записям, расширенным переключателям и случаям, а также модификаторам классов в разделе «Язык» документации Dart.
Справочная документация
Полный пример кода, шаг за шагом, смотрите в репозитории flutter/codelabs
.
Подробные характеристики каждой новой функции можно найти в оригинальной проектной документации: