[Flutter] 다트 언어 마스터하기 – 다트 3.0 신규 문법

이 글은 [Must Have] 코드팩토리의 플러터 프로그래밍 2판에서 발췌했습니다.
골든래빗 출판사
코드팩토리(최지호) 지음

플러터(Flutter)는 다트(Dart) 언어를 사용합니다. 대부분의 플러터 입문자는 별도의 책으로 다트를 공부하지 않고 플러터 서적에서 1개 장 분량으로 얕게 배웁니다. 플러터로 앱을 원활히 개발하려면 다트를 탄탄하게 아는 것이 중요합니다. 그래서 이 책은 또 다른 자료를 찾아보지 않아도 될 정도로 깊이 있게 다트를 다룹니다.

1장에서 다트 입문하기, 2장에서 객체지향 프로그래밍, 3장에서 비동기 프로그래밍, 4장은 다트 3.0 신규 문법을 학습합니다.

 

다트 3.0 신규 문법

플러터 3.0 버전부터는 다트 3.0 버전 이상을 사용합니다. 그리고 다트 언어의 메이저 버전이 3으로 업데이트되면서 새로 추가된 문법들이 생겼습니다. 다트 3.0 버전 업데이트 이후 어떤 문법들이 추가로 생겼는지 알아보겠습니다.

 

1. 레코드

레코드(Record)는 다트 3.0 이상부터 사용할 수 있는 새로운 타입입니다. 레코드는 포지셔널 파라미터(Positional Parameter)나 네임드 파라미터(Named Parameter) 중 한 가지 방식을 적용하여 사용할 수 있습니다. 두 방식은 모두 괄호 안에 쉼표로 구분하여 작성합니다. 자세한 내용은 다음 실제 예를 보며 설명하겠습니다.

 

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

포지셔널 파라미터를 이용한 레코드는 포지셔널 파라미터로 표시한 타입 순서를 반드시 지켜야 합니다. 다음은 String, int 순서로 데이터를 입력해야 하는 레코드를 선언한 예입니다.

 

void main() {
  // 정확한 위치에 어떤 타입의 값이 입력될지 지정할 수 있습니다.
  // (String, int)는 첫 번째 값은 String 타입이고 두 번째 값은 int 타입입니다.
  (String, int) minji = ('민지', 20);
  // ('민지', 20) 출력
  print(minji);
}

 

만약 레코드에 정의한 순서대로 타입을 입력하지 않으면 에러가 발생합니다.

 

void main() {
  // Invalid Assignment 에러
  (String, int) minji = (20, '민지');
  print(minji);
}

 

물론 두 개 이상의 값을 조합해서 레코드를 만들 수도 있습니다. 레코드에 정의할 수 있는 값의 개수에는 제한이 없습니다. 다음은 3개 타입으로 제한한 레코드 예입니다.

 

void main() {
  (String, int, bool) minji = ('민지', 20, true);
  // (민지, 20, true)
  print(minji);
}

 

레코드의 모든 값을 사용하지 않고 특정 순서의 레코드 값을 가져오고 싶다면 ‘$’를 사용하면 됩니다.

 

void main() {
  (String, int, bool) minji = ('민지', 20, true);

  // 민지
  print(minji.$1);

  // 20
  print(minji.$2);

  // true
  print(minji.$3);
}

 

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

네임드 파라미터는 포지셔널 파라미터와는 다르게 입력 순서를 지킬 필요가 없습니다. 다만 네임드 파라미터는 소괄호에 중괄호를 중첩하여 타입과 변수 이름을 쉼표로 구분하고 명시해줘야 합니다.

 

void main() {
  // Named Parameter 형태로 Record를 선언하는 방법이다.
  // 다른 Named Parameter와 마찬가지로 순서는 상관이 없어진다.
  ({String name, int age}) minji = (name: '민지', age: 20);

  // (age: 20, name: 민지) 출력
  print(minji);
}

 

2. 구조 분해

구조 분해(Destructuring)는 값을 반환받을 때 단순히 하나의 변수로 받아오지 않습니다. 반환된 타입을 그대로 복제해서 타입 내부에 각각의 값을 직접 추출해오는 문법입니다. 다트 문법을 공부한 상태라면 코드만 봐도 충분히 이해할 수 있을 것이라 생각되어 이 부분은 별도의 설명을 하지 않겠습니다. 만약 잘 이해가지 않는다면 1단계 ‘다트 언어 마스터하기’로 돌아가 한 번 더 읽어보면 도움이 될 겁니다.

 

2.1 리스트에서의 구조 분해 사용 예제

void main() {
  // 아래 코드와 같지만 한줄에 해결 할 수 있다.
  // final newJeans = ['민지', '해린'];
  // final minji = newJeans[0];
  // final haerin = newJeans[1];
  final [minji, haerin] = ['민지', '해린'];

  // 민지 출력
  print(minji);

  // 해린 출력
  print(haerin);
}

 

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

void main() {
  final numbers = [1, 2, 3, 4, 5, 6, 7, 8];

  // spread operator를 사용하게 되면 중간의 값들을 버릴 수 있다.
  final [x, y, ..., z] = numbers;

  // 1 출력
  print(x);

  // 2 출력
  print(y);

  // 8 출력
  print(z);
}

 

2.3 맵에서의 구조 분해 사용 예제

void main() {
  final minjiMap = {'name': '민지', 'age': 19};
  // Map의 구조와 똑같은 구조로 Destructuring하면 된다.
  final {'name': name, 'age': age} = minjiMap;

  // name: 민지
  print('name: $name');

  // age: 19
  print('age: $age');
}

 

2.4 클래스에서의 구조 분해 사용 예제

void main() {
  final minJiIdol = Idol(name: '민지', age: 19);

  final Idol(name: name3, age: age3) = minJiIdol;

  // 민지 출력
  print(name3);

  // 19 출력
  print(age3);
}

class Idol {
  final String name;
  final int age;

  Idol({
    required this.name,
    required this.age,
  });
}

 

3. switch문

switch문은 다트 언어가 3.0 버전으로 업데이트되면서 스위치 표현식(Switch Expression), 패턴 매칭(Pattern Matching), 완전 확인(Exhaustiveness Checking), 가드 절(Guard Clause) 네 가지가 추가되었습니다. switch문은 다트 언어 버전 업데이트 후 가장 많은 변화가 생긴 문법 중 하나입니다. 소개한 각 기능을 코드와 함께 살펴보겠습니다.

 

3.1 표현식 기능

코드는 표현식(Expression)과 문(Statement)으로 나눌 수 있습니다. 표현식은 어떠한 값을 만들어내는 코드입니다. 예를 들어 1 + 1은 값 2를 만드는 표현식입니다. 이처럼 표현식이 평가되면 새로운 값을 생성하거나 기존 값을 참조합니다.

문은 기본 단위이자 가장 작은 코드 실행 단위로 명령문 즉, 컴퓨터에 내리는 명령이라고 생각하면 됩니다. 쉽게 말해 표현식 여러 개가 모여 문이 되며, 문에는 선언문, 할당문, 반복문 등이 있습니다. 예는 var a = 3처럼 값을 할당하는 코드입니다. 다트 3.0 부터는 switch문을 함수처럼 사용하여 직접 값을 반환받을 수 있는 절 기능이 추가되었습니다. 다음 코드를 보면 더 쉽게 이해할 수 있을 겁니다.

 

void main() {
  String dayKor = '월요일';

  // switch문이 함수처럼 값을 반환합니다.
  String dayEnglish = switch (dayKor) {
    // '=>'를 사용하면 switch문 조건에 맞을 때 값을 반환할 수 있습니다.
    '월요일' => 'Monday',
    '화요일' => 'Tuesday',
    '수요일' => 'Wednesday',
    '목요일' => 'Thursday',
    '금요일' => 'Friday',
    '토요일' => 'Saturday',
    '일요일' => 'Sunday',
    // _는 default와 같은 의미로 사용됩니다.
    _ => 'Not Found',
  };

  // Monday 출력
  print(dayEnglish);
}

 

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() {
  // match: aaa 출력
  switcher('aaa');
  // match: [1, 2] 출력
  switcher([1, 2]);
  // match: [_. _. _] 출력
  switcher([3, 4, 5]);
  // match: [int 6, int 7] 출력
  switcher([6, 7]);
  // match: (String: 민지, int: 19) 출력
  switcher(('민지', 19));
  // no match 출력
  switcher(8);
}

 

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');
  };
}

 

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, 2');
      break;
    default:
      print('default');
  }
}

 

4. 클래스 제한자

다트 3.0 버전에는 다양한 클래스 제한자(Class Modifiers)가 새로 추가됐습니다. 추가된 클래스 제한자는 base, final, interface, sealed, mixin 입니다. 모든 클래스 제한자는 class 키워드 앞에 명시합니다. 클래스 제한자를 명시한 클래스는 해당 클래스를 사용하는 파일이 아닌 다른 파일에 선언해야 정상으로 기능이 작동합니다.

 

4.1 base 제한자

base 제한자는 base 클래스의 기능을 강제하는 제한자입니다. base 키워드를 사용하게 되면 해당 클래스는 오직 상속만 할 수 있게 됩니다. 그리고 base 클래스가 아닌 자식 클래스는 꼭 base, final 또는 sealed 제한자를 함께 사용해줘야 합니다.

 

base class Parent{}

 

import '1_a.dart';

// 인스턴스화 가능
Parent parent = Parent();

// 가능
base class Child extends Parent{}

// subtype of base or final is not base final or sealed 에러
// base / sealed / final modifier중 하나 필요
class Child2 extends Parent{}

// subtype of base or final is not base final or sealed 에러
// base 클래스는 implement 불가능
class Child3 implements Parent{}

 

4.2 final 제한자

final 제한자를 사용하면 같은 파일에서 상속(Extend)과 재정의(Implement)를 할 수 있지만 외부 파일에서는 할 수 없습니다. 그리고 final 제한자는 base 제한자의 기능을 모두 포함합니다.

 

final class Parent{}

 

import '2_a.dart';

// 인스턴스화 가능
Parent parent = Parent();

// extend 불가능
class Child extends Parent{}

// implement 불가능
class Child2 implements Parent{}

 

4.3 interface 제한자

interface 제한자는 클래스를 외부 파일에서 상속받지 못하고 재정의만 할 수 있도록 제한하는 역할을 합니다.

 

interface class Parent{}

 

import '3_a.dart';

// 인스턴스화 가능
Parent parent = Parent();

// extend 불가능
class Child1 extends Parent{}

// implement 가능
class Child2 implements Parent{}

 

4.4 sealed 제한자

sealed 제한자는 sealed 클래스를 파일 외부에서 상속, 재정의 그리고 인스턴스화할 수 없도록 제한합니다.

 

sealed class Parent{}

 

import '4_a.dart';

// 인스턴스화 불가능
Parent parent = Parent();

// extend 불가능
class Child1 extends Parent {}

// implement 불가능
class Child2 implements Parent {}

 

4.5 mixin 제한자

다트 3.0부터는 mixin을 클래스에 사용할 수 있게 되었습니다. 일반 mixin과 같은 역할을 하면서도 상속할 수 있다는 장점이 있습니다.

 

mixin class MixinExample{}

// extend 가능
class Child1 extends MixinExample{}

// mixin으로 사용 가능
class Child2 with MixinExample{}

 

다트 3.0 신규 문법 마무리

다트 3.0 신규 문법에 대해 알아봤습니다. 다트 3.0 이상 버전부터 새롭게 추가된 레코드와 타입 내부의 값을 분해해서 직접 추출해오는 구조 분해가 있습니다. 또한 4가지 문법이 추가된 switch문과 객체지향 프로그래밍 언어인 다트의 캡슐화와 클래스의 고유성을 위해 클래스 제한자를 제공합니다. 플러터 3.0 버전부터는 다트 3.0 버전 이상을 사용하기 때문에 추가된신규 문법을 꼭 학습하고 넘어가길 바랍니다.

 

핵심 요약

  1. 레코드는 새로운 타입으로 네임드 파라미터와 포지셔널 파라미터가 있습니다.
  2. 구조 분해는 타입 내부의 각각의 값을 직접 추출해오는 문법입니다.
  3. switch문에는 표현식, 패턴 매칭, 완전 확인, 가드 절이 추가되어 다양한 방법으로 조건을 확인할 수 있습니다.
  4. 객체지향 프로그래밍 언어의 특징 중 하나인 클래스의 고유성을 위해 다양한 클래스 제한자가 추가되었습니다.

 

지금까지 1장 다트 입문하기, 2장 객체지향 프로그래밍, 3장 비동기 프로그래밍, 4장 다트 3.0 신규 문법까지 총 4개장으로 다트 언어를 학습했습니다. 모든 링크를 아래에 정리해 놓았습니다. 플러터 입문에 앞서 꼭 다트 언어 마스터에 도움되길 바랍니다!

 

다트 입문하기

 

객체지향 프로그래밍

 

비동기 프로그래밍

 

다트 3.0 신규 문법

최지호(코드팩토리) 
임페리얼 칼리지 런던을 졸업하고 계리 컨설팅 회사 밀리만(Milliman) 한국 지사에서 소프트웨어 엔지니어로 일했습니다. 현재 주식회사 코드팩토리를 창업하여 개발을 하면서 초보자뿐만 아니라 현직 개발자에게도 유용한 개발 강의를 제작합니다. 밀리의서재 플러터 전환 차세대 프로젝트를 리드했습니다.

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내