04. 다트 3.0 신규 문법

학습 목표

  • 플러터 3.0 버전 부터는 다트 3.0 버전 이상을 사용함.
  • 그리고 다트 언어의 메이저 버전이 3으로 업데이트되면서 새로 추가된 문법들이 생김.

4.1. 레코드

  • Record: 다트 3.0 이상부터 사용할 수 있는 새로운 타입.
  • 레코드는 positional parameter 또는 named parameter 중 한 가지 방식을 적용하여 사용할 수 있음.

4.1.1. 포지셔널 파라미터를 이용한 레코드

  • 포지셔널 파라미터를 이용한 레코드는 포지셔널 파라미터로 표시한 타입 순서를 반드시 지켜야 함.
  • 다음은 String, int 순서로 데이터를 입력해야 하는 레코드를 선언하는 예:
void main() {
  // 정확한 위치에 어떤 타입의 값이 입력될지 지정할 수 있음.
  (String, int) roo = ('roo', 10);
  
  print(roo); // 결괏값: ('roo', 10)
}
  • 레코드에 정의한 순서대로 타입을 입력하지 않으면 에러 발생함.
  • 두 개 이상의 값을 조합해서 레코드를 만들 수도 있음. 레코드에 정의할 수 있는 값의 개수에는 제한이 없음.
  • 레코드의 모든 값을 사용하지 않고 특정 순서의 레코드 값을 가져오고 싶다면 $ 를 사용하면 됨.
void main() {
  (String, int, bool) roo = ('roo', 10, true);
  
  print(roo.$1); // roo
  print(roo.$2); // 10
  print(roo.$3); // true
}

4.1.2. 네임드 파라미터를 이용한 레코드

  • 네임드 파라미터는 소괄호에 중괄호를 중첩하여 타입과 변수 이름을 쉼표로 구분하고 명시해줘야 함.
void main() {
  ({String name, int age}) roo = (name: 'roo', age: 10);
  
  print(roo); // (age: 10, name: roo)
}

4.2. 구조 분해

  • 구조 분해(destructuring)는 값을 반환받을 때 단순히 하나의 변수로 받아오지 않음.
  • 반환된 타입을 그대로 복제해서 타입 내부에 각각의 값을 직접 추출해오는 문법.

4.2.1. 리스트에서의 구조 분해 사용

void main() {
  final [roo, loo] = ['roo', 'loo'];
  
  print(roo); // roo
  print(loo); // loo
}

4.2.2. 리스트에서의 스프레드 연산자를 이용한 구조 분해 사용

void main() {
  final numbers = [1, 2, 3, 4, 5, 6, 7, 8];
  
  // 스프레드 연산자를 사용해서 중간의 값들을 버리기
  final [x, y, ..., z] = numbers;
  
  print(x); // 1
  print(y); // 2
  print(z); // 8
}

4.2.3. 맵에서의 구조 분해 사용

void main() {
  final rooMap = {'name': 'roo', 'age': 10};
  // 위 구조와 같은 구조로 구조 분해하기
  final {'name': name, 'age': age} = rooMap;
 
  print('name: $name'); // name: roo
  print('age: $age'); // age: 10
}

4.2.4. 클래스에서의 구조 분해 사용

void main() {
  final roo = Idol(name: 'roo', age: 10);
  
  // 클래스의 생성자 구조와 똑같이 구조 분해하기
  final Idol(name: name, age: age) = roo;
  
  print(name);
  print(age);
}

4.3. switch 문

  • switch 문은 다트 언어가 3.0. 버전으로 업데이트되면서 스위치 표현식(switch expression), 패턴 매칭(pattern matching), 완전 확인(exhaustiveness checking), 가드 절(guard clause) 네 가지가 추가 됨.

4.3.1. 표현식 기능

  • 코드는 표현식(expression)과 문(statement)으로 나눌 수 있음.
  • expression:
    • 표현식은 어떠한 값을 만들어내는 코드.
    • 예를 들어 1 + 1은 값 2를 만드는 표현식.
    • 이처럼 표현식이 평가되면 새로운 값을 생성하거나 기존 값을 참조함.
  • statement:
    • 기본 단위이자 가장 작은 코드 실행 단위로 명령문(컴퓨터에 내리는 명령)을 뜻함.
    • 표현식 여러 개가 모여 statement가 됨.
    • statement의 종류: 선언문, 할당문, 반복문
  • 다트 3.0부터는 switch 문을 함수처럼 사용하여 직접 값을 반환받을 수 있는 절 기능이 추가됨.
void main() {
  String dayKor = '월요일';
 
  // switch문이 함수처럼 값을 반환함.
  String dayEnglish = switch (dayKor) {
    // '=>'를 사용하면 switch 문 조건에 맞을 때 값을 반환할 수 있음.
    '월요일' => 'Monday',
    '화요일' => 'Tuesday',
    // _ 는 default와 같은 의미로 사용됨.
    _ => 'Not Found',
  };
 
  print(dayEnglish); // Monday
}

4.3.2. 패턴 매칭

  • 패턴 매칭(pattern matching) = 다트 3.0에 추가된 기능. 특히 switch 문을 사용할 때 패턴 매칭을 통해서 더욱 복잡한 조건을 형성할 수 있어 유용함.
void switcher(dynamic anything) {
  switch (anything) {
    // 정확히 'aaa' 문자열만 매칭
    case 'aaa':
      print('match: aaa');
      break;
    // 정확히 [1, 2] 리스트만 매칭
    case [1, 2]:
      print('match [1, 2]');
      break;
    // 3개의 값이 들어 있는 리스트를 모두 매칭
    case [_,_,_]:
      print('match [_,_,_]');
      break;
    // 첫 번째와 두 번째 값에 int가 입력된 리스트를 매칭
    case [int a, int b]:
      print('match: [int $a, int $b]');
      break;
    // 첫 번째 값에 String, 두 번째 값에 int가 입력된 Record 타입 매칭
    case (String a, int b):
      print('match: (String: $a, int: $b)');
      break;
    default:
      print('no match');
  }
}
 
void main() {
  switcher('aaa');
  switcher([1, 2]);
  switcher([3, 4, 5]);
  switcher([6, 7]);
  switcher(('roo', 10));
  switcher(8) 
}
 
// 결괏값:
// match: aaa
// match [1, 2]
// match [_,_,_]
// match: [int 6, int 7]
// match: (String: roo, int: 10)
// no match

4.3.3. 엄격한 검사

  • 엄격한 검사(exhaustiveness checking)는 코드가 입력받을 수 있는 모든 조건을 전부 확인하고 있는지 체크하는 기술.
  • 다트 3.0에서는 switch 문에 엄격한 검사가 추가되어 모든 조건을 확인하고 있는지 빌드할 때 확인 가능.
void main() {
  // val에 입력될 수 있는 값은 true, false, null
  bool? val;
 
  // null 조건을 입력하지 않았기 때문에
  // non exhaustive switch statement 에러 출력
  // null case를 추가하거나 default case를 추가해야 에러가 사라짐.
  switch (val) {
    case true:
      print('true');
    case false:
      print('false');
  }
}

4.3.4. 보호 구문

  • switch문에는 when 키워드로 보호 구문(guard clause)를 추가할 수 있도록 업데이트 됨.
  • when 키워드는 boolean으로 반환할 조건을 각 case문에 추가할 수 있으며,
  • when 키워드 뒤에 오는 조건이 true를 반환하지 않으면 case 매치가 안됨.
void main() {
  (int a, int b) val = (1, -1);
  
  // default가 출력됨.
  // 만약 b 값을 0 이상으로 변경하면,
  // 1, _ 를 출력함.
  switch (val) {
    case (1, _) when val.$2 > 0:
      print('1, _');
      break;
    default:
      print('default');
  }
}

4.4. 클래스 제한자

  • class modifiers: 다트 3.0에 추가된 클래스 제한자는 base, final, interface, sealed, mixin
  • 모든 클래스 제한자는 class 키워드 앞에 명시함.
  • 클래스 제한자를 명시한 클래스는 해당 클래스를 사용하는 파일이 아닌 다른 파일에 선언해야 정상으로 기능이 작동함.

4.4.1 base 제한자

  • base 제한자는 base 클래스의 기능을 강제하는 제한자.
  • base 키워드를 사용하게 되면 해당 클래스는 오직 상속만 할 수 있게 됨.
  • 그리고 base 클래스가 아닌 자식 클래스는 꼭 base, final 또는 sealed 제한자를 함께 사용해줘야 함.
// lib/4.4/1_a.dart
base class Parent {}
// lib/4.4/1_b.dart
import '1_a.dart';
 
// 인스턴스화 가능
Parent parent = Parent();
 
// rksmd
base class Child extends Parent {}
 
// subtype of base or final is not base final or sealed 에러 출력
// base / sealed / final 제한자 중 하나가 필요.
class Child2 extends Parent {}
 
// subtype of base or final is not base final or sealed 에러 출력
// base 클래스는 implement가 불가능.
class Child3 implements Parent {}

4.4.2. final 제한자

  • final 제한자를 사용하면 같은 파일에서 상속(extend)과 재정의(implement)를 할 수 있지만 외부 파일에서는 할 수 없음.
  • 그리고 final 제한자는 base 제한자의 기능을 모두 포함함.
// lib/4.4/2_a.dart
final class Parent {}
// lib/4.4/2_b.dart
import '2_a.dart';
 
// 인스턴스화 가능
Parent parent = Parent();
 
// extend 불가능
class Child extends Parent {}
 
// implement 불가능
class Child2 implements Parent {}

4.4.3. interface 제한자

  • interface 제한자는 클래스를 외부 파일에서 상속받지 못하고 재정의만 할 수 있도록 제한하는 역할을 함.
// lib/4.4/3_a.dart
interface class Parent {}
// lib/4.4/3_b.dart
import '3_a.dart';
 
// 인스턴스화 가능
Parent parent = Parent();
 
// extend 불가능
class Child1 extends Parent {};
 
// implement 가능
class Child2 implements Parent {}

4.4.4. sealed 제한자

  • sealed 제한자는 sealed 클래스를 파일 외부에서 상속, 재정의 그래고 인스턴스화 할 수 없도록 제한함.
// lib/4.4/4_a.dart
sealed class Parent{}
import '4_a.dart';
 
// 인스턴스화 불가능
Parent parent = Parent();
 
// extend 불가능
class Child1 extends Parent {}
 
// implements 불가능
class Child2 implements Parent {}

4.4.5. mixin 제한자

  • 다트 3.0부터는 mixin을 클래스에 사용할 수 있게 됨.
  • 일반 mixin과 같은 역할을 하면서도 상속할 수 있다는 장점.
// lib/4.4/5.dart
mixin class MixinExample {}
 
// extend 가능
class Child1 extends MixinExample {}
 
// mixin으로 사용 가능
class Child2 with MixinExample {}

핵심 요약

  1. 레코드: 새로운 타입으로 named parameter와 positional parameter 사용해서 생성 가능
  2. 구조 분해: 타입 내부의 각각의 값을 직접 추출해오는 문법
  3. switch 문: 표현식, 패턴 매칭, 완전 확인, 가드 절이 추가되어 다양한 방법으로 조건을 확인할 수 있음.
  4. 클래스 제한자**:** 객체지향 프로그래밍 언어의 특징 중 하나인 클래스의 고유성을 위해 다양한 클래스 제한자가 추가됨.