본문 바로가기

Dart

Dart 3 업데이트 문법

Records

Record는 익명이고 불변인 집합 타입이다. Records는 크기가 고정되어 있고 여러 타입의 개체를 담을 수 있으며

Map, List, Set과 같은 컬렉션에 저장할 수 있다.

 

syntax

Record는 소괄호로 감싸져있고, 안의 요소들은 쉼표로 구분된다. 또한 위에서 말한 것처럼 여러 타입을 담을 수 있다.

Record 값은 dollar sign을 통해서 접근할 수 있다.

var records = ("first", true, 1); // (String, bool, int) records = ("first", true, 1);
print(records.$1); // first
print(records.$2); // true
print(records.$3); // 1

named fields

named field를 이용하여 Record를 선언하고 초기화하면 field를 통해 값에 접근할 수 있다. 또한 named field를 일반 field와 함께 사용할 수 있다. named field를 사용하는 방법은 함수를 선언할 때 named parameter를 지정하는 것과 동일하게 중괄호를 사용하면 된다.

({int a, int b}) record = (a: 1, b: 2); // var record = (a: 1, b: 2);
print(record.a); // 1
print(record.b); // 2

(String name, int age, {bool handsome}) me = ("yong", 28, handsome: false);
// var me = ("yong", 28, handsome: false);

 

 

Record type

Record를 사용하면 순서와 타입이 보장된다.

첫번째 요소인 first에 순서로 접근하며 Type 보장이 되기 때문에 String에서 제공하는 함수를 사용할 수 있음

 

Record equality

두 개의 Record가 같은 형태를 가지고, 상응하는 필드의 값이 동일한 경우 두 Record는 동일하다.

(int a, int b, int c) record1 = (1, 2, 3);
(int d, int e, int f) record2 = (1, 2, 3);
print(record1 == record2); //true

Multiple returns

Record를 사용하면 여러 개의 값을 묶어서 반환할 수 있다. 반환된 Record 값은 Pattern Matching을 통해서 로컬 변수에 destructuring(비구조화)하여 할당 할 수 있다.

void main() {
  final (name, age) = personInfo(json);
  print(name); // "yong"
  print(age); // 28
}

Map<String, dynamic> json = {
  "name" : "yong",
  "age" : 28,
  "handsome" : false,
};

(String, int) personInfo(Map<String, dynamic> json) {
  return (json["name"] as String, json["age"] as int);
}

 

Patterns

Pattern은 맥락과 모양에 따라 값을 매칭하거나, 비구조화하거나 둘 다 수행할 수 있게 한다.

Pattern Matching을 통해 다양한 형태의 destructuring이 가능하다.

final (name, age) = ("yong", 28);
print(name); // "yong"
print(age); // 28

왼쪽 Record와 오른쪽 Record 형태로 매칭을 시키면 name과 age에 값이 할당된다.

 

rest 키워드

리스트를 매칭할 때 ...를 사용하면 특정 범위를 생략하고 매칭이 가능하다.

  final nums = [1, 2, 3, 4, 5, 6];
  final [x, y, ..., z] = nums;
  print(x); // 1
  print(y); // 2
  print(z); // 6

 

 

...뒤에 변수를 붙이면 생략된 값에 접근하여 사용할 수 있다.

final nums = [1, 2, 3, 4, 5, 6];
final [x, y, ...rest, z] = nums;
print(x); // 1
print(y); // 2
print(z) // 6
print(rest) // [3, 4, 5]

 

_를 사용하면 해당 위치의 값을 무시할 수 있다. -> 이를 Wildcard라고 함

final nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
final [x, _, _, y, ...rest, z, _, _] = nums;
print(x);
print(y);
print(z);
print(rest);

 

Map destructuring

Map도 형태만 맞추면 destructuring이 가능하다.

final me = {"name": "yong", "age": 28};
final {"name": name, "age": age} = me;
print(name); // "yong"
print(age); // 28

Class destructuring

void main() {
   final Person(name: name, age: age) = const Person(name: "yong", age: 28);
   print(name); // "yong"
   print(age); // 28
}

class Person {
  final String name;
  final int age;
  const Person({required this.name, required this.age});
}

Pattern Matching

위에서 설명한 destructuring도 Pattern Matching을 사용해서 진행했다. 하지만 Pattern Matching을 Dart 3.0 부터 switch 문을 통해 더 적극적으로 사용할 수 있다.

void switcher(dynamic anything) {
  switch (anything) {
    case [int a, int b]:
      print("int $a, int $b");
    case [_, _, _]:
      print("match [_, _, _]");
    case {"name":_}:
      print("match {'name':_}");
    case Person(name: String name, age: int age):
      print("match person");
    case > 10 && < 14:
      print("match > 10 & < 14");
    default:
      print("no match");
  }
}

 

또한 Dart 3.0부터 switch문 자체를 반환하여 사용할 수 있다. 단, switch 문 내부에서 반환 값이 있어야한다.

String switcher2(dynamic val, bool condition) => switch (val) {
  5 when condition => "match 5",
  _ => "no match",
};

 

case 문을 통해 Pattern Matching을 조건문 내부에서 사용할 수 있다.

final me = {
    "name": "yong",
    "age": 28,
};

if (me case {"name" : String _, "age" : int _}) {
  print("match");
}

Class Modifier

final class

final로 클래스를 선언하면 extends, implements 또는 mixin으로 사용이 불가능하다.(같은 파일 내에서는 가능)

final class Person {
  final String name;
  final int age;

  Person(this.name, this.age);
}

base class

base로 클래스를 선언하면 extend는 가능하지만 implements는 불가능하다.(같은 파일 내부에서는 모두 가능) 또한 base, sealed, final로 선언한 클래스만 extends가 가능하다.

base class Person {
  final String name;
  final int age;

  Person(this.name, this.age);
}

interface

interface로 선언하면 implements만 가능하다.

interface class Person {
  final String name;
  final int age;

  Person(this.name, this.age);
}

sealed class

sealed class는 final이면서 abstract이다. 

sealed class Person{}
class Engineer extends Person {}
class Singer extends Person {}

그리고 패턴 매칭을 활용할 수 있다.(exhaustive class)

String practice(Person person) => switch(person) { // 컴파일 에러
	Engineer e => "engineer",
}

String practice(Person person) => switch(person) { // 컴파일 에러 해결
	Engineer e => "engineer",
    _ => "가수" // == Singer s => "가수"
}

mixin class

mixin class에는 extends나 with를 사용할 수 없다.(mixin과 동일) 또한 class는 on 키워드를 사용할 수 없기 때문에 mixin class도 on 키워드를 사용할 수 없다.

mixin class PersonMixin {
	void think() {
    	...
    }
}

'Dart' 카테고리의 다른 글

Dart의 동시성 정리 - Isolate  (0) 2023.11.17
Dart Compiler  (0) 2023.02.20