1. 소개
Dart 3은 언어, 즉 문법의 새로운 주요 카테고리에 패턴을 도입합니다. Dart 코드를 작성하는 이 새로운 방법 외에도 일부 다른 언어의 향상된 기능(예: 다양한 유형의 데이터를 번들로 묶는 레코드, 액세스 제어를 위한 클래스 수정자 및 새로운 switch 표현식과 if-case 문)이 있습니다.
이러한 특징은 Dart 코드를 작성할 때 선택의 폭을 넓혀줍니다. 이 Codelab에서는 이러한 기능을 사용하여 코드를 더 작고 간소하며 유연하게 만드는 방법을 알아봅니다.
이 Codelab은 개발자가 Flutter 및 Dart와 친숙하다고 가정하지만, 필수는 아닙니다. 시작하기 전에 다음 리소스로 기본사항에 대해 알아보는 것이 좋습니다.
빌드할 항목
이 Codelab은 Flutter로 JSON 문서를 표시하는 애플리케이션을 만듭니다. 애플리케이션은 외부 소스에서 가져온 JSON을 시뮬레이션합니다. JSON에는 수정 날짜, 제목, 헤더, 문단 등 문서 데이터가 포함됩니다. Flutter 위젯이 데이터를 필요로 하는 곳마다 데이터가 전송되고 압축해제될 수 있도록 데이터를 레코드로 적절하게 압축하는 코드를 작성합니다.
그런 다음, 값이 패턴과 일치할 때 해당 패턴을 사용하여 적절한 위젯을 빌드합니다. 또한 패턴을 사용하여 데이터를 로컬 변수로 디스트럭처링하는 방법도 알아봅니다.
학습할 내용
- 다양한 유형의 여러 값을 저장하는 레코드를 만드는 방법
- 레코드를 사용하여 함수에서 여러 값을 반환하는 방법
- 패턴을 사용하여 레코드와 다른 객체의 데이터를 일치시키고 확인하고 디스트럭처링하는 방법
- 패턴에 일치하는 값을 새 변수 또는 기존 변수에 결합하는 방법
- 새로운 switch 문 기능, switch 표현식, if-case 문을 사용하는 방법
- 포괄성 검사를 활용하여 모든 사례가 switch 문 또는 switch 표현식에서 처리되도록 하는 방법
2. 환경 설정
- Flutter SDK 설치
- Visual Studio Code(VS Code)와 같은 편집기 설정
- 최소 하나의 타겟 플랫폼(iOS, Android, 데스크톱 또는 웹브라우저)에 맞는 플랫폼 설정 단계 진행
3. 프로젝트 만들기
패턴, 레코드, 기타 새로운 기능을 알아보기 전에 잠시 시간을 내어 코드를 작성할 환경과 간단한 Flutter 프로젝트를 설정합니다.
Dart 가져오기
- Dart 3을 사용하고 있는지 확인하려면 다음 명령어를 실행합니다.
flutter channel stable flutter upgrade dart --version # This should print "Dart SDK version: 3.0.0" or higher
Flutter 프로젝트 만들기
flutter create
명령어를 사용하여patterns_codelab
이라는 새 프로젝트를 만듭니다.--empty
플래그를 사용하면lib/main.dart
파일에 있는 표준 Counter 앱(내용을 삭제해야 함)을 만들지 않습니다.
flutter create --empty patterns_codelab
- 그런 다음, VS Code를 사용하여
patterns_codelab
디렉터리를 엽니다.
code patterns_codelab
최소 SDK 버전 설정
- Dart 3 이상을 사용하도록 프로젝트에 SDK 버전 제약사항 설정
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 요청과 같은 외부 소스에서 데이터를 수신하는 프로그램을 상상해 보세요. 이 Codelab에서는 documentJson
변수에 여러 줄의 문자열이 포함된 JSON 수신 데이터를 모의 처리하여 더 현실적인 사용 사례를 간소화합니다.
이 JSON 데이터는 Document
클래스에 정의되어 있고 이 Codelab 후반부에서는 파싱된 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,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title goes here'),
),
body: Column(
children: [
Center(
child: Text('Body goes here'),
),
],
),
);
}
}
다음 두 개의 위젯을 앱에 추가했습니다.
DocumentApp
: UI의 테마를 지정하기 위해 최신 버전의 Material Design을 설정합니다.DocumentScreen
:Scaffold
위젯을 사용하여 페이지의 시각적 레이아웃을 제공합니다.
- 모든 것이 원활하게 실행되는지 확인하려면 Run and Debug(실행 및 디버그)를 클릭하여 호스트 머신에서 앱을 실행합니다.
- 기본적으로 Flutter는 어느 타겟 플랫폼이든지 사용할 수 있는 플랫폼을 선택합니다. 타겟 플랫폼을 변경하려면 상태 표시줄에서 현재 플랫폼을 선택하세요.
DocumentScreen
위젯에 정의된 title
과 body
요소가 포함된 빈 프레임이 표시됩니다.
5. 레코드 생성 및 반환
이 단계에서는 레코드를 사용하여 함수 호출에서 여러 값을 반환합니다. 그런 다음, 값에 액세스하고 UI에 레코드를 반영하도록 DocumentScreen
위젯에서 해당 함수를 호출합니다.
레코드 생성 및 반환
data.dart
에서 Document 클래스에 하나의 레코드를 반환하는getMetadata
라는 새 함수를 추가합니다.
lib/data.dart
(String, {DateTime modified}) getMetadata() {
var title = "My Document";
var now = DateTime.now();
return (title, modified: now);
}
이 함수의 반환 유형은 두 개의 필드를 가진 레코드로, 하나의 유형은 String
이고 다른 하나의 유형은 DateTime
입니다.
return 문은 괄호 안의 두 개의 값을 묶어(예: (title, modified: now)
) 새 레코드를 구성합니다.
첫 번째 필드는 위치로 나타내고 이름이 지정되어 있지 않으며 두 번째 필드는 modified
라고 이름이 지정되어 있습니다.
레코드 필드 액세스
DocumentScreen
위젯에서는 레코드를 가져와서 레코드 값에 액세스할 수 있도록build
메서드에서getMetadata()
을 호출합니다.
lib/main.dart
@override
Widget build(BuildContext context) {
var metadataRecord = document.getMetadata();
return Scaffold(
appBar: AppBar(
title: Text(metadataRecord.$1),
),
body: Column(
children: [
Center(
child: Text(
'Last modified ${metadataRecord.modified}',
),
),
],
),
);
}
getMetadata()
함수는 레코드를 반환하며 이 레코드는 로컬 변수 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()
메서드는 문자열을 첫 번째 인수로 사용합니다.modified
필드는 DateTime이고 문자열 보간 유형을 사용하여String
으로 변환됩니다.
다양한 유형의 데이터를 유형에 안전하게 반환하는 다른 방법은 더 상세한 정보를 가진 클래스를 정의하는 것입니다.
6. 패턴 일치 및 디스트럭처링
레코드는 효율적으로 다양한 유형의 데이터를 수집하고 쉽게 주변에 전달합니다. 이제 패턴을 사용하여 코드를 개선하세요.
패턴은 하나 이상의 값이 사용할 수 있는 구조(예: 청사진)를 나타냅니다. 패턴은 실제 값과 비교하여 서로 일치하는지 확인합니다.
패턴과 값이 일치하는 일부 패턴은 패턴에서 데이터를 추출하여 일치하는 값을 디스트럭처링합니다. 디스트럭처링 방식을 사용하면 객체에서 값을 압축해제하여 로컬 변수에 할당하거나 값에 매칭을 계속 실행합니다.
로컬 변수에 레코드 디스트럭처링
DocumentScreen
의build
메서드를 리팩터링하여getMetadata()
를 호출하고 리팩터링한 메서드를 사용하여 패턴 변수 선언을 초기화합니다.
lib/main.dart
@override
Widget build(BuildContext context) {
var (title, :modified) = document.getMetadata(); // New
return Scaffold(
appBar: AppBar(
title: Text(title), // New
),
body: Column(
children: [
Center(
child: Text(
'Last modified $modified', // New
),
),
],
),
);
}
레코드 패턴 (title, :modified)
에는 getMetadata()
에서 반환하는 레코드 필드와 일치하는 두 개의 변수 패턴이 포함됩니다.
- 결과는 두 개의 필드(이 중 하나는
modified
로 이름이 지정된 필드)가 포함된 레코드이므로 표현식은 하위 패턴과 일치합니다. - 표현식과 하위 패턴이 일치하므로 변수 선언 패턴은 표현식을 디스트럭처링하며 값에 액세스하여 같은 유형과 이름(
String title
,DateTime modified
)의 새로운 로컬 변수로 결합합니다.
변수 패턴 :modified
의 문법은 modified: modified
의 약식 표기입니다. 다른 이름의 새 로컬 변수를 만들려면 modified: localModified
라고 쓰면 됩니다.
- 이전 단계와 같은 결과를 보려면 핫 리로드하세요. 동작은 정확히 동일합니다. 코드를 더 간결하게 만들었을 뿐입니다.
7. 패턴을 사용하여 데이터 추출
특정 문맥에서 패턴은 일치하는지 확인하고 디스트럭처링하는 것뿐만 아니라 패턴이 일치하는지 여부에 따라 코드가 해야 하는 작업에 관해 결정도 합니다. 이를 반박 가능 패턴(refutable pattern)이라고 합니다.
앞선 단계에서 사용한 변수 선언 패턴은 반박 불가 패턴(irrefutable pattern)입니다. 값은 패턴과 일치해야 하며 그렇지 않으면 오류입니다. 이 경우 디스트럭처링하지 않습니다. 모든 변수 선언이나 할당을 생각해 보세요. 같은 유형이 아니면 값을 변수에 할당할 수 없습니다.
반면, 반박 가능 패턴은 다음과 같이 제어 흐름 문맥에서 사용됩니다.
- 이 패턴은 비교하려는 값이 일치하지 않을 것이라고 예상합니다.
- 즉, 값이 일치하는지에 따라 제어 흐름에 영향을 줍니다.
- 이 패턴은 값이 일치하지 않아도 오류로 인해 실행이 중단되지 않습니다. 곧바로 다음 문으로 이동합니다.
- 패턴이 일치할 때만 사용할 수 있는 변수를 디스트럭처링하고 결합하게 됩니다.
패턴을 사용하지 않고 JSON 값 읽기
이 섹션에서는 JSON 데이터와 연동하는 데 패턴을 어떻게 사용할 수 있을지 알아보기 위해 패턴 매칭을 사용하지 않고 데이터를 읽습니다.
- 이전 버전의
getMetadata()
를_json
맵에서 값을 읽어오는 버전으로 교체합니다. 이 버전의getMetadata()
를Document
클래스에 복사하여 붙여넣습니다.
lib/data.dart
(String, {DateTime modified}) getMetadata() {
if (_json.containsKey('metadata')) {
var metadataJson = _json['metadata'];
if (metadataJson is Map) {
var title = metadataJson['title'] as String;
var localModified = DateTime.parse(metadataJson['modified'] as String);
return (title, modified: localModified);
}
}
throw const FormatException('Unexpected JSON');
}
이 코드는 데이터가 패턴을 사용하지 않고 올바르게 구조화되어 있는지 확인합니다. 이후 단계에서는 더 적은 코드를 사용하여 동일한 확인 작업을 실행하기 위해 패턴 매칭을 사용합니다. 다른 작업을 하기 전에 세 가지 검사를 실행합니다.
- JSON에는 예상 데이터 구조(예:
if (_json.containsKey('metadata'))
)가 포함됩니다. - 데이터는 예상 유형(예:
if (metadataJson is Map)
)이 있습니다. - 이전 검사에서 암시적으로 확인된 데이터는 null이 아닙니다.
맵 패턴을 사용하여 JSON 값 읽기
반박 가능 패턴을 통해 JSON이 맵 패턴을 사용한 예상 구조를 포함하고 있는지 확인할 수 있습니다.
- 이전 버전의
getMetadata()
를 다음 코드로 교체합니다.
lib/data.dart
(String, {DateTime modified}) getMetadata() {
if (_json
case {
'metadata': {
'title': String title,
'modified': String localModified,
}
}) {
return (title, modified: DateTime.parse(localModified));
} else {
throw const FormatException('Unexpected JSON');
}
}
여기에서 Dart 3에 도입된 새로운 종류의 if 문, if-case를 볼 수 있습니다. case 본문은 사례 패턴이 _json
의 데이터와 일치할 때만 실행됩니다. 이러한 일치는 수신된 JSON을 확인하기 위해 첫 번째 버전의 getMetadata()
에서 작성한 검사와 동일한 검사를 사용합니다. 이 코드는 다음을 확인합니다.
_json
이 Map 유형임_json
에metadata
키가 포함됨_json
은 null이 아님_json['metadata']
도 Map 유형임_json['metadata']
에title
과modified
키가 포함됨title
과localModified
는 문자열이며 null이 아님
값이 일치하지 않으면 패턴은 반박하며(실행을 계속하지 않음) else
절을 진행합니다. 성공적으로 일치하는 경우 패턴은 title
과 modified
값을 맵에서 디스트럭처링하고 새로운 로컬 변수로 결합합니다.
패턴의 전체 목록은 기능 사양의 패턴 섹션에 있는 표를 참고하세요.
8. 더 많은 패턴을 위한 앱 준비
지금까지 JSON 데이터의 metadata
부분을 해결했습니다. 이 단계에서는 blocks
목록의 데이터를 처리하고 앱에 이 데이터를 렌더링하기 위해 비즈니스 로직을 약간 미세하게 조정합니다.
{
"metadata": {
// ...
},
"blocks": [
{
"type": "h1",
"text": "Chapter 1"
},
// ...
]
}
데이터를 저장하는 클래스 만들기
- JSON 데이터의 블록 중 하나에 맞게 데이터를 읽고 저장하는 데 사용할 새 클래스(
Block
)를data.dart
에 추가합니다.
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': var type, 'text': var text}) {
return Block(type, text);
} else {
throw const FormatException('Unexpected JSON format');
}
}
}
팩토리 생성자 fromJson()
은 앞서 본 맵 패턴과 동일한 if-case를 사용합니다.
키 중 하나인 checked
가 패턴에서 처리되지 않더라도 json
은 맵 패턴과 일치합니다. 맵 패턴은 패턴에서 명시적으로 처리하지 않는 맵 객체의 모든 항목을 무시합니다.
블록 객체 목록 반환
- 다음으로 새 함수
getBlocks()
를Document
클래스에 추가합니다.getBlocks()
는 JSON을Block
클래스의 인스턴스로 파싱하고 블록 목록을 반환하여 UI에 렌더링합니다.
lib/data.dart
List<Block> getBlocks() {
if (_json case {'blocks': List blocksJson}) {
return <Block>[
for (var blockJson in blocksJson) Block.fromJson(blockJson)
];
} else {
throw const FormatException('Unexpected JSON format');
}
}
getBlocks()
함수는 UI를 빌드하기 위해 나중에 사용할 Block
객체의 목록을 반환합니다. 익숙한 if-case 문은 검사를 실행하고 blocks
메타데이터 값을 blocksJson
이라는 새 List
에 전송합니다(패턴을 사용하지 않음, 전송할 toList()
메서드 필요).
목록 리터럴에는 Block
객체로 새 목록을 채우기 위해 컬렉션 for가 포함됩니다.
이 섹션에서는 이 Codelab에서 이미 시도했던 패턴 관련 기능을 사용하지 않습니다. 다음 단계에서는 UI에 목록 항목을 렌더링할 준비를 합니다.
9. 패턴을 사용하여 문서 표시
이제 if-case 문과 반박 가능 패턴을 사용하여 JSON 데이터를 성공적으로 디스트럭처링하고 재구성합니다. 하지만 if-case는 패턴을 사용하는 제어 흐름 구조를 개선하는 방법 중 하나일 뿐입니다. 이제 반박 가능 패턴에 관한 지식을 switch 문에 적용해 보세요.
switch 문으로 패턴을 사용하여 렌더링 항목 제어
main.dart
에 새 위젯BlockWidget
을 만들어 위젯의type
필드에 따라 각 블록의 스타일을 결정합니다.
lib/main.dart
class BlockWidget extends StatelessWidget {
final Block block;
const BlockWidget({
required this.block,
Key? key,
}) : super(key: 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
메서드의 switch 문은 block
객체의 type
필드에 따라 전환됩니다.
- 첫 번째 case 문은 상수 문자열 패턴을 사용합니다.
block.type
이 상숫값h1
과 같으면 패턴은 일치합니다. - 두 번째 case 문은 하위 패턴과 마찬가지로 두 개의 상수 문자열 패턴이 있는 논리 연산 OR 패턴을 사용합니다.
block.type
이 하위 패턴p
또는checkbox
중 하나와 일치하면 패턴은 일치합니다.
- 마지막 case는 와일드 카드 패턴
_
입니다. switch 문의 case에서 와일드 카드는 모든 것과 일치합니다. 와일드 카드는 switch 문에서 계속 허용되고 있는default
절(약간 더 상세해짐)과 동일하게 작동합니다.
와일드 카드 패턴은 패턴이 허용되는 곳은 어디서나 사용할 수 있습니다(예: var (title, _) = document.getMetadata();
와 같은 변수 선언 패턴).
이 문맥에서 와일드 카드는 변수와 결합하지 않습니다. 두 번째 필드를 삭제합니다.
다음 섹션에서는 Block
객체를 표시한 후 더 많은 switch 기능을 알아봅니다.
문서 콘텐츠 표시
DocumentScreen
위젯의 build
메서드에서 getBlocks()
를 호출하여 Block
객체 목록을 포함하는 로컬 변수를 만듭니다.
DocumentationScreen
에 있는 기존의build
메서드는 아래 버전으로 교체합니다.
lib/main.dart
@override
Widget build(BuildContext context) {
var (title, :modified) = document.getMetadata();
var blocks = document.getBlocks(); // New
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
// New
Text('Last modified: $modified'),
Expanded(
child: ListView.builder(
itemCount: blocks.length,
itemBuilder: (context, index) {
return BlockWidget(block: blocks[index]);
},
),
),
],
),
);
}
BlockWidget(block: blocks[index])
줄은 getBlocks()
메서드에서 반환하는 블록 목록의 항목마다 BlockWidget
위젯을 생성합니다.
- 애플리케이션을 실행한 후 화면에 블록이 표시되는 것을 볼 수 있습니다.
10. switch 표현식 사용
패턴은 switch
와 case
에 많은 기능을 추가합니다. 더 많은 위치에서 이 기능을 유용하게 만들기 위해 Dart는 switch 표현식을 제공합니다. 일련의 case 문은 변수 할당 또는 return 문에 직접 값을 제공할 수 있습니다.
switch 문을 switch 표현식으로 변환
Dart 분석기는 코드에 변경사항을 적용할 수 있도록 지원합니다.
- 이전 섹션의 switch 문으로 커서를 이동합니다.
- 제공되는 지원을 확인하려면 전구 모양 아이콘을 클릭하세요.
- Convert to switch expression(switch 표현식으로 변환) 지원을 선택합니다.
이 코드의 새 버전은 다음과 같습니다.
TextStyle? textStyle;
textStyle = switch (block.type) {
'h1' => Theme.of(context).textTheme.displayMedium,
'p' || 'checkbox' => Theme.of(context).textTheme.bodyMedium,
_ => Theme.of(context).textTheme.bodySmall
};
switch 표현식은 switch 문과 비슷해 보입니다. 하지만 case
키워드를 제거했고 case 본문에서 패턴을 분리하기 위해 =>
를 사용합니다. switch 문과 달리 switch 표현식은 값을 반환하고 표현식을 사용할 수 있는 곳 어디에서나 사용할 수 있습니다.
11. 객체 패턴 사용
Dart는 객체 지향 언어이므로 모든 객체에 패턴이 적용됩니다. 이 단계에서는 UI의 날짜 렌더링 로직을 개선하기 위해 객체 패턴을 변환하고 객체 속성을 디스트럭처링합니다.
객체 패턴에서 속성 추출
이 섹션에서는 패턴을 사용하여 마지막으로 수정된 날짜를 표시하는 방식을 개선합니다.
formatDate
메서드를main.dart
에 추가합니다.
lib/main.dart
String formatDate(DateTime dateTime) {
var today = DateTime.now();
var difference = dateTime.difference(today);
return switch (difference) {
Duration(inDays: 0) => 'today',
Duration(inDays: 1) => 'tomorrow',
Duration(inDays: -1) => 'yesterday',
Duration(inDays: var days, isNegative: true) => '${days.abs()} days ago',
Duration(inDays: var days) => '$days days from now',
};
}
이 메서드는 difference
값에 따라 변환되는 switch 표현식, Duration
객체를 반환합니다. 이 객체는 JSON 데이터에서 today
와 modified
값 사이의 기간을 나타냅니다.
switch 표현식의 각 사례는 객체의 inDays
와 isNegative
속성에 대해 getter를 호출하여 일치하는 객체 패턴을 사용하고 있습니다. 이 구문은 Duration 객체를 생성할 수 있는 것처럼 보이지만 실제로는 difference
객체의 필드에 액세스하는 것입니다.
첫 번째 세 개의 사례는 객체 속성 inDays
와 일치하는 상수 하위 패턴 0
, 1
, -1
을 사용하고 이에 대응하는 문자열을 반환합니다.
마지막 두 개의 사례는 오늘, 어제, 내일 이상의 기간을 처리합니다.
isNegative
속성이 불리언 상수 패턴true
와 일치하면, 즉 수정된 날짜가 과거이면 며칠 전인지 표시됩니다.- 이 경우 차이를 포착하지 않으면 기간은 양수로 된 일수가 되어야 하므로(
isNegative: false
를 사용하여 명시적으로 확인할 필요는 없음) 수정된 날짜는 미래이고 지금부터의 일수를 표시합니다.
주(week)에 형식을 지정하는 로직 추가
- UI가 7일이 넘는 기간을 주로 표시하도록 이 기간을 식별하기 위해 새로운 두 가지 사례를 형식 지정 함수에 추가합니다.
lib/main.dart
String formatDate(DateTime dateTime) {
var today = DateTime.now();
var difference = dateTime.difference(today);
return switch (difference) {
Duration(inDays: 0) => 'today',
Duration(inDays: 1) => 'tomorrow',
Duration(inDays: -1) => 'yesterday',
Duration(inDays: var days) when days > 7 => '${days ~/ 7} weeks from now', // New
Duration(inDays: var days) when days < -7 => '${days.abs() ~/ 7} weeks ago', // New
Duration(inDays: var days, isNegative: true) => '${days.abs()} days ago',
Duration(inDays: var days) => '$days days from now',
};
}
이 코드는 검사 절을 사용합니다.
- 검사 절은 케이스 패턴 뒤에
when
키워드를 사용합니다. - 검사 절은 if-case 문, switch 문, switch 표현식에서 사용할 수 있습니다.
- 검사 절은 패턴이 일치한 후에만 조건을 패턴에 추가합니다.
- 검사 절이 false이면 전체 패턴을 반박하고 실행은 다음 사례로 넘어갑니다.
UI에 새로 형식이 지정된 날짜 추가
- 마지막으로,
DocumentScreen
에build
메서드를 업데이트하여formatDate
함수를 사용합니다.
lib/main.dart
@override
Widget build(BuildContext context) {
var (title, :modified) = document.getMetadata();
var formattedModifiedDate = formatDate(modified); // New
var blocks = document.getBlocks();
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Text('Last modified: $formattedModifiedDate'), // New
Expanded(
child: ListView.builder(
itemCount: blocks.length,
itemBuilder: (context, index) =>
BlockWidget(block: blocks[index]),
),
),
],
),
);
}
- 앱에서 변경사항을 확인하려면 다음과 같이 핫 리로드합니다.
12. 포괄적 switch를 위한 클래스 봉인
마지막 switch의 끝부분에는 와일드 카드나 기본 사례를 사용하지 않았습니다. 실행되지 못할 수도 있는 값에 대한 사례를 항상 포함하는 것은 좋지만, 정의한 사례가 사용할 가능성이 있는 모든 가능한 값 inDays
를 처리하는 것을 알고 있으므로 이와 같은 간단한 예에서는 포함하지 않아도 괜찮습니다.
switch 문에서 모든 사례가 처리되는 경우 이를 포괄적 switch라고 합니다. 예를 들어, switch 문에 true
와 false
에 대한 사례가 있다면 bool
유형에 따른 전환은 포괄적입니다. 마찬가지로 enum 값 각각에 대해 사례가 있는 경우 enum
유형에 따른 전환은 포괄적입니다. enum은 상숫값의 고정된 숫자를 나타내기 때문입니다.
Dart 3은 새 클래스 수정자인 sealed
를 사용하여 객체와 클래스 계층 구조에 대한 포괄성 검사를 확장했습니다. Block
클래스를 봉인된 슈퍼클래스로 리팩터링하세요.
서브클래스 만들기
data.dart
에Block
을 확장한 세 개의 새로운 클래스,HeaderBlock
,ParagraphBlock
,CheckboxBlock
을 만듭니다.
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를 switch 표현식으로 리팩터링하여 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
키워드는 클래스 수정자로, 같은 라이브러리에서만 이 클래스를 확장하거나 구현할 수 있음을 의미합니다. 분석기에서 이 클래스의 하위유형을 알고 있으므로 switch 문에서 하위유형 중 하나를 처리하는 데 실패하거나 switch 문이 포괄적이지 않으면 오류가 보고됩니다.
위젯을 표시하기 위해 switch 표현식 사용
- 각 사례에 대해 객체 패턴을 사용하는 switch 표현식으로
main.dart
의 BlockWidget 클래스를 업데이트합니다.
lib/main.dart
class BlockWidget extends StatelessWidget {
final Block block;
const BlockWidget({
required this.block,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(8),
child: switch (block) {
HeaderBlock(:var text) => Text(
text,
style: Theme.of(context).textTheme.displayMedium,
),
ParagraphBlock(:var text) => Text(text),
CheckboxBlock(:var text, :var isChecked) => Row(
children: [
Checkbox(value: isChecked, onChanged: (_) {}),
Text(text),
],
),
},
);
}
}
BlockWidget
의 첫 번째 버전에서는 TextStyle
을 반환하기 위해 Block
객체의 필드에 따라 switch 문을 적용했습니다. 이제 Block
객체 자체의 인스턴스에 따라 swtich 표현식을 적용하고 서브클래스를 나타내는 객체 패턴에 일치하는지 확인하여 이 과정에서 객체의 속성을 추출합니다.
Dart 분석기는 Block
이 봉인 클래스가 됐기 때문에 switch 표현식에서 각 서브클래스를 처리하는지 검사할 수 있습니다.
또한, 여기에서 switch 표현식을 사용하면 전에는 별도의 반환 문이 필요했던 것과 달리 결과를 child
요소에 바로 전달할 수 있습니다.
- 처음에 렌더링된 체크박스 JSON 데이터를 보려면 핫 리로드하세요.
13. 축하합니다
패턴, 레코드, 개선된 switch와 case 및 봉인 클래스를 사용한 실험을 성공적으로 마쳤습니다. 많은 내용을 다루었지만 이러한 기능을 간략하게 살펴본 것에 불과합니다. 패턴에 관한 자세한 내용은 기능 사양을 참고하세요.
다양한 패턴 유형과 패턴이 표시될 수 있는 다양한 문맥 및 하위 패턴의 잠재적 중첩은 수많은 동작의 가능성을 만들어 냅니다. 또한, 확인하기도 쉽습니다.
패턴을 사용하여 Flutter로 콘텐츠를 표현하는 모든 종류의 방법을 상상해 볼 수 있습니다. 패턴을 사용하면 몇 줄의 코드로 UI를 빌드할 수 있도록 데이터를 안전하게 추출할 수 있습니다.
다음 단계
- Dart 문서의 언어 섹션에서 패턴, 레코드, 개선된 switch와 case, 클래스 수정자에 관한 문서를 확인해 보세요.
참조 문서
저장소에서 전체 샘플을 확인해 보세요.
각각의 새로운 기능에 대한 자세한 사양은 아래의 원본 디자인 문서를 확인해 보세요.