Dart 튜토리얼 36편: Dart Cheatsheet(핵심 문법 빠르게 복습)
요약
핵심 요지
- 문제 정의: 문법이 손에 안 익으면, 같은 일을 더 길고 복잡하게 구현하게 된다.
- 핵심 주장: 이 글은 “테이블로 빠르게 찾고, 접기/펼치기에서 코드로 확인”하는 치트시트 형태로 핵심 문법을 정리한다.
- 주요 근거: 빠른 인덱스(테이블)에서 핵심 문법/패턴을 정리하였다.
- 실무 기준: “지금 당장 쓰는 문법(문자열·null·컬렉션) → 함수 파라미터 → 예외/생성자” 순서로 복습한다.
참고 자료
- Dart cheatsheet
- Dart 튜토리얼 1편 ~ 35편
문제 상황
치트시트는 “빠르게 찾는 것”이 목적입니다.
그래서 이 글은 긴 설명 대신, 자주 쓰는 문법을 표로 먼저 정리하고, 필요한 것만 펼쳐서 예제 코드로 확인하는 구조를 택합니다.
해결 방법
빠른 인덱스(테이블)
| 주제 | 핵심 문법/패턴 | 한 줄 설명 | 펼쳐보기 |
|---|---|---|---|
| 변수/상수 | var, final, const, late | 값 바인딩(가변/불변/지연 초기화) | 보기 |
| 기본 타입 | bool, int, double, num, String | 숫자/불리언/문자열 핵심 타입 | 보기 |
| 타입 시스템 | Object, dynamic, Never, void | 타입 상단/동적/불가능/반환 없음 | 보기 |
| 주석 | //, /* */, /// | 코드 설명과 API 문서 주석 | 보기 |
| 애노테이션 | @Deprecated, @override | 선언에 추가 정보를 붙임 | 보기 |
| import / export | import, as, show/hide | 라이브러리 심볼 가져오기/제한 | 보기 |
| 연산자 모음 | 산술/비교/논리/비트/대입 | 계산/조건/비트 작업을 표현 | 보기 |
| 문자열 보간 | ${expression} / $identifier | 문자열 안에 값을 삽입 | 보기 |
| 문자열 리터럴 | raw(r''), multi-line('''/""") | 이스케이프 무시/여러 줄 문자열 | 보기 |
| nullable 변수 | T? | null 가능 타입 선언 | 보기 |
| null-aware 연산자 | ??, ??= | null 대체/한 번만 대입 | 보기 |
| 조건부 접근 | ?. | null이면 접근/호출을 생략 | 보기 |
| null assertion | expr! | null이 아님을 단언(위험) | 보기 |
| 타입 테스트/캐스트 | is, is!, as | 런타임 타입 검사/변환 | 보기 |
| assert | assert(condition) | 개발 중 조건 검증 | 보기 |
| 분기 | if, switch, ?:, switch expression | 조건에 따라 실행/값 선택 | 보기 |
| 반복 | for, for-in, while, do-while, break, continue | 반복 실행과 흐름 제어 | 보기 |
| 라벨 | label:, break label, continue label | 중첩 반복을 한 번에 제어 | 보기 |
| 컬렉션 리터럴 | [], {}, {'k': v}, <T>[] | List/Set/Map을 빠르게 생성 | 보기 |
| 컬렉션 if/for | if (...) ..., for (...) ... | 리터럴 안에서 조건/반복 생성 | 보기 |
| 스프레드 | ..., ...? | 다른 컬렉션 원소를 펼쳐 추가 | 보기 |
| 화살표 문법 | => | 한 줄 함수(표현식 반환) | 보기 |
| 함수 파라미터 | optional/required/named | 파라미터 설계(기본값/필수) | 보기 |
| 익명 함수/클로저 | (x) { ... }, 캡처 | 함수를 값처럼 전달/상태 캡처 | 보기 |
| 제너레이터 | sync*, yield, yield* | 여러 값을 순차 생성 | 보기 |
| typedef | typedef F = ... | 복잡한 타입에 이름 붙이기 | 보기 |
| cascades | .., ?.. | 같은 객체에 작업을 연속 적용 | 보기 |
| getter / setter | get, set | 필드 접근에 계산/검증 추가 | 보기 |
| 옵션 위치 파라미터 | [] | 위치 인자를 선택적으로 받기 | 보기 |
| 이름 있는 파라미터 | {} / required | 호출 시 이름으로 전달 | 보기 |
| Records | (a, b), (x: 1, y: 2) | 여러 값을 한 덩어리로 전달 | 보기 |
| Generics | <T>, List<T>, class Box<T> | 타입을 파라미터처럼 재사용 | 보기 |
| 클래스 기본 | class, abstract, implements, with | 객체/상속/구현/조합 | 보기 |
| 생성자 모음 | default/named/factory/redirecting/const | 초기화 방식 패턴 모음 | 보기 |
| 정적 멤버 | static | 클래스에 귀속되는 멤버 | 보기 |
| 프라이빗(가시성) | _name | 라이브러리 단위 private | 보기 |
| 오버라이드 | @override, super | 상속 메서드 재정의/호출 | 보기 |
| operator / call | operator +, call() | 연산자/함수 호출처럼 동작 | 보기 |
| mixin | mixin, mixin class | 기능을 조합해서 재사용 | 보기 |
| enum | enum | 고정된 값 집합 | 보기 |
| extension | extension | 기존 타입에 메서드 추가 | 보기 |
| extension type | extension type | 새 타입처럼 보이게 감싸기 | 보기 |
| class modifiers | base, interface, final, sealed | 타입 재사용을 제한해 API 보호 | 보기 |
| patterns | switch/case/destructure | 형태 매칭으로 분기/추출 | 보기 |
| 예외 처리 | try/on/catch/rethrow/finally | 오류를 잡고 정리/전파 | 보기 |
| async/await | Future, async, await | 비동기 코드를 순서대로 작성 | 보기 |
| Stream | Stream, async*, yield | 여러 이벤트를 시간에 따라 처리 | 보기 |
Variables & constants(변수/상수) — var, final, const, late
설명: 값이 변할 수 있으면 변수, 바뀌지 않게 하려면 상수를 사용합니다.
final1은 “한 번만 대입”, const2는 “컴파일 타임 상수”라는 차이가 있습니다.
late3는 “나중에 초기화”할 때 사용합니다.
var count = 0; // 타입 추론(type inference)[^type-inference]count = 1;
final now = DateTime.now(); // 실행 시점에 값이 결정됨// now = DateTime.now(); // 다시 대입 불가
const pi = 3.141592; // 컴파일 타임 상수
late final String token;token = 'abc123'; // 늦게 초기화(한 번만 대입)주의: const는 “실행해봐야 알 수 있는 값”(예: 현재 시간)을 담을 수 없습니다.
Built-in types(기본 타입) — bool, int, double, num, String
설명: Dart의 기본 타입(built-in types)4에는 숫자(int, double, num), 불리언(bool), 문자열(String) 등이 포함됩니다.
bool ok = true;
int a = 1;double b = 1.5;num c = a + b; // num은 int/double을 모두 표현
String s = 'hello';주의: int와 double은 num의 하위 타입입니다.
Type system(타입 시스템) — Object, dynamic, Never, void
설명: Dart 타입 시스템5에서 자주 보는 타입 몇 가지를 예제로 정리합니다.
Object any = 'text'; // 어떤 객체든 담을 수 있음
dynamic x = 1; // 정적 타입 검사에서 더 느슨한 타입x = 'now string';
void logIt(Object v) { print(v);}
Never fail(String msg) { throw StateError(msg);}주의: dynamic은 강력하지만, 분석기/자동 완성의 도움을 줄이고 실행 중 오류로 이어지기 쉬워서 남용을 피하는 흐름이 자주 제시됩니다.
Comments(주석) — //, /* */, ///
설명: 한 줄 주석은 //, 여러 줄 주석은 /* ... */을 사용합니다.
API 문서 주석(documentation comments)6은 /// 또는 /** ... */ 형태를 씁니다.
// 한 줄 주석
/*여러 줄 주석*/
/// 문서 주석: 이 함수가 무엇을 하는지 설명합니다.int add(int a, int b) => a + b;주의: 문서 주석은 dart doc로 API 문서를 만들 때 함께 사용됩니다.
Metadata(애노테이션) — @something
설명: 메타데이터(metadata)7는 선언에 추가 정보를 붙이는 문법입니다.
대표적으로 @Deprecated와 @override 같은 애노테이션이 소개됩니다.
class Base { void run() {}}
class Child extends Base { @override void run() {}
@Deprecated('대신 newRun을 사용하세요.') void oldRun() {}
void newRun() {}}주의: 애노테이션은 “컴파일러/분석기/도구가 참고하는 정보”로 쓰이는 경우가 많습니다.
import / export — import, as, show/hide
설명: 라이브러리를 불러오려면 import를 사용합니다.
이름 충돌을 피하려면 as로 접두어를 붙이고, 공개 범위를 줄이려면 show/hide를 사용할 수 있습니다.
import 'dart:math' as math;
double area(double r) => math.pi * r * r;import 'package:my_pkg/my_pkg.dart' show ApiClient;// import 'package:my_pkg/my_pkg.dart' hide InternalHelper;주의: import는 “파일 포함”이 아니라 “라이브러리 심볼을 가져오는 것”입니다.
Operators(연산자 모음) — 산술/비교/논리/비트/대입
설명: Dart의 연산자8는 값을 계산하고, 비교하고, 조건을 구성하는 데 쓰입니다.
여기서는 “자주 등장하는 것”을 한 번에 모아둡니다.
// 산술final a = 7 + 3;final b = 7 - 3;final c = 7 * 3;final d = 7 / 3; // doublefinal e = 7 ~/ 3; // 정수 나눗셈(몫)final f = 7 % 3; // 나머지// 비교/동등final gt = 3 > 2;final eq = 3 == 3;final ne = 3 != 4;// 논리final ok = (1 < 2) && (2 < 3);final nope = (1 < 2) || (2 > 3);final neg = !(1 < 2);// 비트/시프트final and = 0b1100 & 0b1010;final or = 0b1100 | 0b1010;final xor = 0b1100 ^ 0b1010;final shl = 1 << 3;final shr = 8 >> 2;// 복합 대입var x = 1;x += 2;x *= 3;주의: ~/는 “정수 몫”을 만드는 연산자이고, /는 결과가 double입니다.
String interpolation(문자열 보간) — ${expression} / $identifier
설명: 문자열 안에 값(또는 표현식)을 넣을 때 ${expression}을 사용합니다.
표현식이 단순 식별자(identifier)9라면 {}를 생략해서 $myVar처럼 쓸 수 있습니다.
String stringify(int x, int y) { return '$x $y';}주의: 디버깅 출력이라면 값만 찍기보다 'x=$x y=$y'처럼 “이름+값” 형태가 더 읽기 쉽습니다.
String literals(문자열 리터럴) — raw, multi-line
설명: raw string10은 r'...'처럼 r을 붙여 백슬래시 이스케이프를 해석하지 않습니다.
여러 줄 문자열(multi-line string)11은 ''' 또는 """로 만들 수 있습니다.
final rawPath = r'C:\\tmp\\file.txt';final text = '''첫 줄둘째 줄''';주의: raw string에서는 \n 같은 이스케이프가 그대로 문자로 들어갑니다.
Nullable variables(nullable 변수) — T?
설명: Dart는 sound null safety12를 강제합니다.
즉, 기본은 non-nullable이고, null이 가능하면 타입에 ?를 붙여 T?로 선언합니다.
int? a; // 초기값은 nullString? name = 'Jane';String? address; // 초기값은 null주의: int a = null;처럼 non-nullable 타입에 null을 넣을 수 없습니다.
Null-aware operators(null-aware 연산자) — ??, ??=
설명: 값이 null일 수 있을 때, “대체값” 또는 “한 번만 대입”을 간단히 표현합니다.
int? a; // = nulla ??= 3;print(a); // 3
a ??= 5;print(a); // 3 (이미 값이 있으니 유지)String? foo = 'a string';String? bar; // = null
String? baz = foo ?? bar; // foo가 null이면 bar주의: ??=는 “현재 값이 null일 때만” 대입합니다.
Conditional property access(조건부 접근) — ?.
설명: 객체가 null일 수 있을 때, 속성/메서드 접근 전에 ?.를 사용하면 null인 경우 접근을 건너뜁니다.
String? upperCaseIt(String? str) { return str?.toUpperCase();}주의: myObject?.someProperty?.someMethod()처럼 여러 번 이어서 쓸 수 있습니다.
Null assertion(null 단언) — expr!
설명: 값이 nullable이지만, “이 지점에서는 null이 아니라고 확신한다”면 !를 붙여 non-nullable로 다룰 수 있습니다.
int lengthOfName(String? name) { // name이 null이면 runtime error가 납니다. return name!.length;}주의: !는 null이면 실행 중 오류가 나므로, 가능한 경우 if (name == null) return 0; 같은 안전한 처리나 ?./?? 패턴이 더 적합할 수 있습니다.
Type test / cast(타입 테스트/캐스트) — is, is!, as
설명: 런타임 타입을 확인하려면 is/is!를 사용합니다.
캐스트가 필요하면 as를 사용합니다.
String describe(Object x) { if (x is int) { return 'int: $x'; } if (x is String) { return 'String: $x'; } return 'other';}void useAs(Object x) { final s = x as String; // String이 아니면 runtime error print(s.toUpperCase());}주의: as는 실패 시 실행 중 오류가 날 수 있으므로, 보통 is로 먼저 검사하는 흐름이 함께 등장합니다.
assert — assert(condition)
설명: assert13는 “개발 중에만” 조건을 검사하는 용도로 자주 등장합니다.
특히 생성자 initializer list에서 입력 검증 예시로 함께 소개됩니다.
int firstCharCode(String s) { assert(s.isNotEmpty); return s.codeUnitAt(0);}class NonNegativePoint { final int x; final int y;
NonNegativePoint(this.x, this.y) : assert(x >= 0), assert(y >= 0);}주의: assert는 런타임 검증을 “항상” 보장하는 장치가 아닐 수 있어, 입력을 반드시 막아야 한다면 예외 처리 같은 방식이 더 적합할 수 있습니다.
Branches(분기) — if, switch, ?:, switch expression
설명: 조건에 따라 실행 흐름을 나눌 때 if/else 또는 switch를 씁니다.
값을 고르는 목적이라면 ?:(conditional operator)14나 switch expression15을 사용할 수 있습니다.
String grade(int score) { if (score >= 90) { return 'A'; } if (score >= 80) { return 'B'; } return 'C';}String label(bool isOk) => isOk ? 'OK' : 'NO';String toMessage(String command) { switch (command) { case 'OPEN': return '열기'; case 'CLOSE': return '닫기'; default: return '알 수 없음'; }}String describeNum(int n) { // switch expression은 값을 바로 만들 때 쓸 수 있습니다. return switch (n) { 0 => 'zero', 1 => 'one', _ => 'many', };}주의: switch expression은 patterns16와 함께 쓰이는 흐름이 많아서, 처음에는 switch 문부터 익히는 편이 덜 헷갈립니다.
Loops(반복) — for, for-in, while, do-while, break, continue
설명: 같은 작업을 여러 번 수행할 때 반복문을 씁니다.
int sumTo(int n) { var sum = 0; for (var i = 1; i <= n; i += 1) { sum += i; } return sum;}String joinLines(List<String> lines) { var out = ''; for (final line in lines) { if (line.isEmpty) continue; // 빈 줄은 건너뛰기 out = out.isEmpty ? line : '$out\\n$line'; } return out;}int firstPositive(List<int> nums) { var i = 0; while (i < nums.length) { if (nums[i] > 0) return nums[i]; i += 1; } return 0;}int tries(int target) { var n = 0; do { n += 1; if (n == target) break; // 조건을 만족하면 종료 } while (true); return n;}주의: break는 “반복 종료”, continue는 “이번 회차만 건너뛰기”입니다.
Labels(라벨) — label:, break label, continue label
설명: 중첩 반복에서 바깥 반복을 제어하고 싶으면 라벨을 사용할 수 있습니다.
int findFirst(List<List<int>> matrix, int target) { outer: for (final row in matrix) { for (final v in row) { if (v == target) return v; if (v < 0) continue outer; // 음수를 만나면 다음 row로 } } return -1;}주의: 라벨은 강력하지만 흐름이 복잡해질 수 있어, 정말 필요한 경우에만 쓰는 흐름이 일반적입니다.
Collection literals(컬렉션 리터럴) — List/Set/Map
설명: List/Set/Map을 리터럴로 만들 수 있고, 타입 추론(type inference)17이 동작합니다.
비어 있는 컬렉션은 타입이 추론되지 않을 수 있어 <T>로 타입을 명시하는 패턴이 함께 쓰입니다.
final aListOfStrings = ['one', 'two', 'three'];final aSetOfStrings = {'one', 'two', 'three'};final aMapOfStringsToInts = {'one': 1, 'two': 2, 'three': 3};final aListOfInts = <int>[];final aSetOfInts = <int>{};final aMapOfIntToDouble = <int, double>{};주의: 하위 타입으로 초기화하더라도 상위 타입 컬렉션으로 만들고 싶다면 <BaseType>[SubType(), ...]처럼 타입을 명시합니다.
Collection if/for(컬렉션 안에서 if/for) — 리터럴 안의 if/for
설명: List/Set/Map 리터럴 안에서 if와 for를 쓸 수 있습니다.
조건에 따라 항목을 넣거나, 반복으로 항목을 생성할 때 자주 쓰입니다.
List<String> buildMenu(bool isAdmin) { return [ 'home', if (isAdmin) 'admin', 'settings', ];}List<int> squaresUpTo(int n) { return [ for (var i = 1; i <= n; i += 1) i * i, ];}Map<String, int> indexOf(List<String> items) { return { for (var i = 0; i < items.length; i += 1) items[i]: i, };}주의: 컬렉션 리터럴 안의 if/for는 “값을 만든 뒤 필터링”보다, “만드는 순간부터 조건/반복을 적용”하는 느낌이라 코드가 짧아지는 경우가 많습니다.
Spread(스프레드) — ..., ...?
설명: 다른 컬렉션의 원소를 “펼쳐서” 넣을 때 ...를 사용합니다.
nullable 컬렉션이면 ...?로 null일 때는 아무 것도 추가하지 않는 패턴이 소개됩니다.
List<int> merge(List<int> a, List<int> b) { return [ ...a, ...b, ];}List<String> safeMerge(List<String>? maybe, List<String> base) { return [ ...base, ...?maybe, ];}주의: ...은 “원소 단위로 복사해 넣는 표현”입니다. (원본 리스트를 그대로 참조하는 의미가 아닙니다.)
Arrow syntax(화살표 문법) — =>
설명: =>는 오른쪽 표현식을 평가해 반환하는 “한 줄 함수” 문법입니다.
bool hasEmpty = ['one', '', 'three'].any((s) => s.isEmpty);String joinWithCommas(List<String> strings) => strings.join(',');주의: => 오른쪽에는 “표현식”이 와야 합니다. (블록 { ... }를 넣는 문법이 아닙니다.)
Function parameters(함수 파라미터) — required/optional/named
설명: 파라미터는 크게 positional과 named로 나뉘고, optional 여부/기본값/required 여부를 조합해 설계합니다.
// required positionalint add(int a, int b) => a + b;
// optional positionalint sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) { return a + b + c + d + e;}
// named (기본값/nullable/required 조합)String greet({required String name, String? title, String suffix = '님'}) { final t = title == null ? '' : '$title '; return '$t$name$suffix';}주의: “옵션 위치([])”와 “named({})”는 동시에 섞을 수 없습니다.
Anonymous functions & closures(익명 함수/클로저)
설명: 함수는 값으로 다룰 수 있고, 익명 함수(람다)를 변수에 담아 전달할 수 있습니다.
또한 바깥 스코프의 변수를 캡처(capture)18하는 형태를 클로저(closure)19라고 부릅니다.
int apply(int x, int Function(int) f) => f(x);
void main() { final doubled = apply(10, (n) => n * 2); print(doubled); // 20}Function makeCounter() { var count = 0; return () { count += 1; return count; };}
void main() { final c = makeCounter(); print(c()); // 1 print(c()); // 2}주의: 클로저가 외부 변수를 오래 잡고 있으면, 기대보다 오래 살아남는 상태가 생길 수 있습니다.
Generators(제너레이터) — sync*, yield, yield*
설명: 제너레이터(generator)20는 값을 “여러 번” 만들어 내는 함수 패턴입니다.
동기 시퀀스는 sync*, 비동기 시퀀스는 async*와 함께 yield를 사용합니다.
Iterable<int> oddsUpTo(int n) sync* { for (var i = 1; i <= n; i += 2) { yield i; }}Iterable<int> concat(Iterable<int> a, Iterable<int> b) sync* { yield* a; yield* b;}주의: yield*는 다른 이터러블/스트림의 값을 “그대로 이어서” 내보내는 문법입니다.
Typedefs(타입 별칭) — typedef
설명: 함수 타입이나 복잡한 타입에 “이름”을 붙여 재사용할 수 있습니다.
typedef IntMapper = int Function(int);
int apply(IntMapper f, int x) => f(x);typedef Json = Map<String, Object?>;주의: callable objects21처럼 “함수처럼 호출되는 객체”와 함께 등장하는 경우도 있습니다.
Cascades — .., ?..
설명: 같은 객체에 대한 여러 작업을 한 번에 이어서 쓰고 싶을 때 ..를 사용합니다.
객체가 null일 수 있으면 ?..(null-shorting cascade)을 시작점으로 사용해 null일 때는 전체 작업을 건너뜁니다.
class BigObject { int anInt = 0; String aString = ''; List<double> aList = []; bool _done = false;
void allDone() => _done = true;}
BigObject fillBigObject(BigObject obj) { return obj ..anInt = 1 ..aString = 'String!' ..aList.add(3) ..allDone();}주의: cascades의 “결과”는 마지막 호출의 반환값이 아니라, 원래 객체 참조입니다.
Getters and setters(getter/setter) — get, set
설명: 단순 필드 대신 “검증/계산”을 끼워 넣고 싶을 때 getter와 setter를 정의합니다.
class ShoppingCart { List<double> _prices = [];
double get total => _prices.fold(0, (e, t) => e + t);
set prices(List<double> value) { if (value.any((p) => p < 0)) { throw InvalidPriceException(); } _prices = value; }}
class InvalidPriceException {}주의: setter에서 검증에 실패하면 예외를 던질 수 있습니다.
Optional positional parameters(옵션 위치 파라미터) — []
설명: 위치 기반 파라미터를 옵션으로 만들려면 []로 감쌉니다.
옵션 위치 파라미터는 항상 파라미터 목록의 뒤에 오고, 기본값을 지정하지 않으면 default는 null입니다.
String joinWithCommas(int a, [int? b, int? c, int? d, int? e]) { var total = '$a'; if (b != null) total = '$total,$b'; if (c != null) total = '$total,$c'; if (d != null) total = '$total,$d'; if (e != null) total = '$total,$e'; return total;}주의: 옵션 파라미터를 쓸 때는 null 체크가 자연스럽게 따라옵니다.
Named parameters(이름 있는 파라미터) — {}, required
설명: 파라미터를 이름으로 전달하고 싶으면 {}로 named parameters를 정의합니다.
named parameters는 기본적으로 optional이며, 필요하면 required로 강제할 수 있습니다.
void printName(String firstName, String lastName, {String? middleName}) { print('$firstName ${middleName ?? ''} $lastName');}class MyDataObject { final int anInt; final String aString; final double aDouble;
MyDataObject({this.anInt = 1, this.aString = 'Old!', this.aDouble = 2.0});
MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) { return MyDataObject( anInt: newInt ?? anInt, aString: newString ?? aString, aDouble: newDouble ?? aDouble, ); }}주의: 한 함수에서 “옵션 위치 파라미터([])”와 “named parameters({})”를 동시에 가질 수 없습니다.
Records — (a, b), (x: 1, y: 2)
설명: records22는 여러 값을 “한 덩어리”로 묶어 전달/반환할 때 쓰는 문법입니다.
positional 형태와 named 형태를 모두 가질 수 있습니다.
(int, int) swap((int, int) pair) { final (a, b) = pair; return (b, a);}({int x, int y}) moveRight(({int x, int y}) point) { return (x: point.x + 1, y: point.y);}주의: final (a, b) = pair;처럼 record를 “풀어서 받는” 문법은 patterns16 흐름과 함께 등장합니다.
Generics(제네릭) — <T>
설명: generics23는 “타입을 파라미터처럼 받는” 문법입니다.
List<String>처럼 컬렉션의 원소 타입을 고정하거나, class Box<T>처럼 재사용 가능한 타입을 만들 때 사용합니다.
class Box<T> { final T value; const Box(this.value);}
Box<int> a = const Box(1);Box<String> b = const Box('ok');T first<T>(List<T> items) => items.first;주의: 빈 컬렉션을 만들 때 <T>[]처럼 타입을 명시하는 패턴도 generics의 한 형태입니다.
Classes(클래스 기본) — class, abstract class, extends, implements, with
설명: 클래스는 상태(필드)와 동작(메서드)을 묶는 기본 단위입니다.
상속은 extends, 인터페이스 구현은 implements, 믹스인은 with를 사용합니다.
abstract class Animal { void speak();}
mixin Walker { void walk() { print('걷는다'); }}
class Dog extends Animal with Walker { @override void speak() { print('멍멍'); }}
class RobotDog implements Animal { @override void speak() { print('삐빅'); }}주의: implements는 “구현을 상속받는 것”이 아니라 “인터페이스를 만족하도록 직접 구현하는 것”입니다.
Constructors(생성자 모음) — default/named/factory/redirecting/const
설명: 생성자는 인스턴스를 만드는 방법(초기화 흐름)을 정의합니다.
이 문서(치트시트)는 “자주 등장하는 형태”만 한 번에 모아둡니다.
class Point { final int x; final int y;
// default constructor + this. 파라미터 const Point(this.x, this.y);
// named constructor + initializer list Point.origin() : x = 0, y = 0;
// redirecting constructor const Point.square(int v) : this(v, v);
// factory constructor factory Point.fromJson(Map<String, int> json) { return Point(json['x']!, json['y']!); }}주의: initializer list24는 final 필드 초기화나 assert 같은 사전 검증에 자주 사용됩니다.
static(정적 멤버)
설명: static 멤버는 인스턴스가 아니라 클래스에 속합니다.
class Counter { static int created = 0; int value = 0;
Counter() { created += 1; }}
void main() { Counter(); Counter(); print(Counter.created); // 2}주의: static은 공유 상태(shared state)가 되기 쉬워서, 테스트/동시성에서 주의가 필요할 수 있습니다.
Privacy(프라이빗/가시성) — _name
설명: 식별자 앞에 _를 붙이면 라이브러리 단위로 private25가 됩니다.
class BankAccount { int _balance = 0;
void deposit(int amount) { _balance += amount; }}주의: Dart의 private는 “클래스 단위”가 아니라 “라이브러리 단위”라는 흐름이 자주 강조됩니다.
@override & super(오버라이드)
설명: 상속 관계에서 메서드를 재정의할 때 @override를 사용합니다.
부모 구현을 호출하려면 super.method()를 사용할 수 있습니다.
class Base { String greet() => 'hi';}
class Child extends Base { @override String greet() => '${super.greet()}!';}주의: @override는 문법 오류를 막고(의도한 오버라이드인지) 분석기 경고를 줄이는 데 도움을 줍니다.
operator / call — operator +, call()
설명: 클래스는 일부 연산자를 오버로드(operator overloading)26할 수 있습니다.
또한 call() 메서드를 정의하면 인스턴스를 함수처럼 호출(callable objects)21할 수 있습니다.
class Vec2 { final int x; final int y; const Vec2(this.x, this.y);
Vec2 operator +(Vec2 other) => Vec2(x + other.x, y + other.y);}class Adder { final int delta; const Adder(this.delta);
int call(int x) => x + delta;}
void main() { final add10 = Adder(10); print(add10(5)); // 15}주의: operator 오버로드는 “읽는 사람에게 자연스럽게 이해되는 경우”에만 쓰는 흐름이 안전합니다.
Mixins — mixin, mixin class
설명: 믹스인은 “다중 상속 없이 기능을 섞는” 방식으로 소개됩니다.
mixin은 믹스인 정의, with는 적용입니다.
mixin LoggerMixin { void log(String msg) { print('LOG: $msg'); }}
class Service with LoggerMixin { void run() { log('start'); }}주의: 믹스인은 상속과 달리 “클래스 계층을 깊게 만들지 않고” 기능을 조합하는 데 쓰입니다.
Enums — enum
설명: enum은 고정된 값 집합을 표현합니다.
enum Status { ready, running, done }
String label(Status s) { return switch (s) { Status.ready => '준비', Status.running => '진행', Status.done => '완료', };}주의: switch에서 enum을 다루면, 경우를 빠뜨렸을 때 경고/에러가 도움을 줄 수 있습니다.
Extension methods — extension
설명: 기존 타입에 메서드를 “추가한 것처럼” 보이게 하려면 extension methods를 사용합니다.
extension StringX on String { bool get isBlank => trim().isEmpty;}
bool ok(String s) => !s.isBlank;주의: 같은 이름의 extension이 여러 개 있으면, 어떤 것이 적용되는지 헷갈릴 수 있습니다.
Extension types — extension type
설명: extension type은 “기존 표현을 감싸서 새 타입처럼 보이게” 만들되, 런타임 오버헤드를 줄이는 방향의 기능으로 소개됩니다.
extension type EmailAddress(String value) { bool get isValid => value.contains('@');}
void main() { print(email.isValid);}주의: extension type은 런타임에서 “별도 래퍼 객체가 없을 수 있다”는 점이 함께 설명됩니다.
Class modifiers — base, interface, final, sealed
설명: class modifiers는 “다른 라이브러리/패키지에서 이 타입을 어떻게 재사용할 수 있는지”를 제한해 API 안정성을 높이는 흐름으로 소개됩니다.
// 예시: 같은 패키지 안에서만 상속 가능(개념 예시)base class BaseApi {}
// 예시: 구현은 가능하지만 상속은 제한(개념 예시)interface class ApiShape {}
// 예시: 상속/구현을 막아서 확장 포인트를 닫음(개념 예시)final class Closed {}
// 예시: 같은 라이브러리에서만 서브타입을 허용(개념 예시)sealed class Result {}주의: modifiers는 “문법”이지만, 실제로는 “API 설계 규칙”과 함께 다뤄지는 경우가 많습니다.
Patterns — case, destructure, if-case
설명: patterns16는 값을 “형태로 매칭”하는 문법입니다.
예를 들어 record나 list/map 같은 구조에서 일부를 꺼내거나, 특정 형태일 때만 분기하는 흐름이 소개됩니다.
String describePair((int, int) p) { return switch (p) { (0, 0) => 'origin', (final x, 0) => 'x=$x on x-axis', (0, final y) => 'y=$y on y-axis', _ => 'other', };}void ifCase(Object value) { if (value case (int x, int y)) { print('record: x=$x y=$y'); }}주의: patterns는 종류가 많습니다. (list/map/record/object patterns 등)
처음에는 “record 분해 + switch/if-case” 정도만 익히고, 필요한 패턴 타입은 pattern types27에서 추가로 확인하는 흐름이 안전합니다.
Exceptions(예외) — try / on / catch / rethrow / finally
설명: 예외를 던질 수 있고(throw), 처리할 때는 try/on/catch를 사용합니다.
완전히 처리할 수 없으면 rethrow로 다시 던지고, 무조건 실행해야 하는 정리는 finally에 둡니다.
try { breedMoreLlamas();} on OutOfLlamasException { buyMoreLlamas();} on Exception catch (e) { print('알 수 없는 예외: $e');} catch (e) { print('정말 알 수 없는 값: $e');}try { breedMoreLlamas();} catch (e) { print('예외가 발생했지만, 여기서는 로그만 남긴다.'); rethrow;} finally { cleanLlamaStalls();}주의: “모든 예외가 반드시 잡혀야 한다”는 규칙은 없습니다. (unchecked)
async/await — Future, async, await
설명: 시간이 걸리는 작업(네트워크/파일/타이머 등)을 다룰 때 Future28와 async/await29를 사용합니다.
await는 Future가 끝날 때까지 “현재 함수의 다음 줄”을 잠시 멈추고 결과를 받습니다.
Future<String> loadUserName() async { // 예시를 단순화하기 위해 즉시 완료되는 Future를 사용합니다. final name = await Future.value('Jane'); return 'user=$name';}주의: await는 async 함수 안에서만 쓸 수 있습니다.
Stream — Stream, async*, yield
설명: Future가 “한 번의 결과”라면, Stream30은 “여러 번의 값(이벤트)”을 시간에 따라 전달합니다.
값을 여러 번 내보내는 함수는 async*와 yield를 함께 쓰는 패턴이 소개됩니다.
Stream<int> countUpTo(int n) async* { for (var i = 1; i <= n; i += 1) { yield i; }}Future<int> sumStream(Stream<int> s) async { var sum = 0; await for (final v in s) { sum += v; } return sum;}주의: await for는 stream 이벤트를 순서대로 처리하는 문법이고, 이 역시 async 함수 안에서 사용합니다.
Using this in a constructor(생성자에서 this. 사용)
설명: 생성자 파라미터로 받은 값을 필드에 바로 대입할 때 this.fieldName을 사용할 수 있습니다.
named parameters에서도 같은 방식이 가능하고, 기본값이 있으면 required를 생략할 수 있습니다.
class MyColor { int red; int green; int blue;
MyColor(this.red, this.green, this.blue);}class MyColor { int red; int green; int blue;
MyColor({required this.red, required this.green, required this.blue});}class MyColor { int red; int green; int blue;
MyColor([this.red = 0, this.green = 0, this.blue = 0]);}주의: required는 “null이 될 수 없는 값”을 필수로 받도록 만드는 용도로 자주 등장합니다.
Initializer lists(initializer list) — : 뒤에서 초기화
설명: 생성자 본문이 실행되기 전에 해야 하는 초기화는 initializer list에 둡니다.
또한 개발 중에만 체크할 조건은 assert로 둘 수 있습니다.
class FirstTwoLetters { final String letterOne; final String letterTwo;
FirstTwoLetters(String word) : assert(word.length >= 2), letterOne = word[0], letterTwo = word[1];}주의: final 필드는 생성자 본문 전에 값이 결정되어야 하므로 initializer list가 특히 유용합니다.
Named constructors(이름 있는 생성자) — Class.name()
설명: 한 클래스에 여러 생성자를 두고 싶을 때, Class.name() 형태의 named constructor를 정의합니다.
class Point { double x, y;
Point(this.x, this.y);
Point.origin() : x = 0, y = 0;}
final p = Point.origin();주의: named constructor에서도 initializer list를 사용할 수 있습니다.
Factory constructors(factory 생성자) — factory
설명: factory 생성자는 “항상 같은 클래스 인스턴스를 만든다”는 제약이 없습니다.
즉, subtype을 반환하거나, 입력에 따라 다른 구현을 반환하는 패턴이 가능합니다.
class Square extends Shape {}class Circle extends Shape {}
class Shape { Shape();
factory Shape.fromTypeName(String typeName) { if (typeName == 'square') return Square(); if (typeName == 'circle') return Circle(); throw ArgumentError('Unrecognized $typeName'); }}주의: 입력이 예상 범위를 벗어나면 예외를 던지는 흐름이 자주 함께 나옵니다.
Redirecting constructors(리다이렉트 생성자) — : this(...)
설명: 생성자의 목적이 “다른 생성자로 위임”인 경우, 본문 없이 : 뒤에서 this(...)로 넘깁니다.
class Automobile { String make; String model; int mpg;
Automobile(this.make, this.model, this.mpg);
Automobile.hybrid(String make, String model) : this(make, model, 60);
Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');}class Color { int red; int green; int blue;
Color(this.red, this.green, this.blue);
Color.black() : this(0, 0, 0);}주의: 리다이렉트 생성자는 “중복 초기화”를 줄이는 데 유용합니다.
Const constructors(const 생성자) — const + final
설명: 인스턴스가 바뀌지 않는(immutable) 클래스라면 const 생성자를 만들어 컴파일 타임 상수로 사용할 수 있습니다.
이를 위해 인스턴스 변수는 final이어야 합니다.
class ImmutablePoint { static const ImmutablePoint origin = ImmutablePoint(0, 0);
final int x; final int y;
const ImmutablePoint(this.x, this.y);}class Recipe { final List<String> ingredients; final int calories; final double milligramsOfSodium;
const Recipe(this.ingredients, this.calories, this.milligramsOfSodium);}주의: const로 만든 리스트는 수정할 수 없어서, add 같은 호출에서 오류가 나는 흐름이 함께 제시됩니다.
Footnotes
-
final(final): 변수에 값을 “한 번만” 대입하도록 제한하는 키워드다. ↩
-
const(const): 컴파일 타임에 값이 결정되는 상수를 선언하는 키워드다. ↩
-
late(late): 변수를 “나중에 초기화”하도록 선언하는 키워드다. ↩
-
built-in types(기본 타입): Dart가 기본으로 제공하는 핵심 타입(예: bool, int, double, num, String 등)이다. ↩
-
type system(타입 시스템): 값과 타입의 관계, 검사 규칙, null safety 등을 포함한 타입 규칙 체계다. ↩
-
documentation comments(문서 주석):
///또는/** ... */로 작성하며, API 문서 생성 도구가 읽을 수 있는 주석 형식이다. ↩ -
metadata(메타데이터): 선언에
@Something형태로 붙여 추가 정보를 제공하는 문법이다. ↩ -
operators(연산자): 값의 계산/비교/결합 등을 표현하는 기호 및 키워드 집합이다. ↩
-
identifier(식별자): 변수/함수/클래스 이름처럼 프로그램 요소를 가리키는 이름이다. ↩
-
raw string(raw 문자열):
r'...'형태로, 이스케이프 시퀀스를 처리하지 않고 그대로 문자열로 취급하는 문자열 리터럴이다. ↩ -
multi-line string(여러 줄 문자열):
'''또는"""로 여러 줄에 걸친 문자열을 만드는 문법이다. ↩ -
sound null safety(사운드 널 안전성): null이 가능한지 타입 수준에서 구분하고, null 관련 오류를 컴파일/분석 단계에서 줄이도록 강제하는 모델이다. ↩
-
assert(assert): 개발 중 조건을 검사하고, 실패 시 오류를 내는 디버그용 검사 문법이다. ↩
-
conditional operator(조건 연산자):
condition ? a : b형태로 조건에 따라 값을 선택하는 표현이다. ↩ -
switch expression(switch 표현식):
switch (...) { ... }형태로 값을 만들어 반환하는 표현식이다. ↩ -
patterns(패턴): 값의 형태를 기준으로 매칭하고, 필요한 부분을 꺼내는 문법/규칙 집합이다. ↩ ↩2 ↩3
-
type inference(타입 추론): 명시 타입 없이도 컴파일러가 값과 문맥으로 타입을 결정하는 기능이다. ↩
-
capture(캡처): 함수가 자신이 정의된 바깥 스코프의 변수를 “사용(참조)”하는 것을 말한다. ↩
-
closure(클로저): 캡처된 변수(환경)18를 포함해, 함수가 바깥 스코프의 상태를 함께 유지하는 형태다. ↩
-
generators(제너레이터):
sync*/async*와yield로 값을 여러 번 생성해 내보내는 함수 패턴이다. ↩ -
callable objects(호출 가능한 객체): 인스턴스에
call()을 정의해 함수처럼 호출할 수 있는 객체다. ↩ ↩2 -
records(records): 여러 값을 하나로 묶어 전달/반환할 수 있는 값 타입 문법이다. ↩
-
generics(제네릭): 타입을 파라미터처럼 받아 재사용 가능한 함수/클래스를 만들 수 있게 하는 문법이다. ↩
-
initializer list(초기화 리스트): 생성자 본문 실행 전에
:뒤에서 필드를 초기화하거나assert를 실행하는 구간이다. ↩ -
privacy(프라이빗/가시성):
_name처럼 밑줄로 시작하는 식별자를 라이브러리 밖에서 숨기는 규칙이다. ↩ -
operator overloading(연산자 오버로딩): 클래스에서
operator +같은 연산자 메서드를 정의해 연산자 동작을 커스터마이즈하는 기능이다. ↩ -
pattern types(패턴 타입): patterns에서 사용할 수 있는 패턴 종류(list/map/record/object 등)를 분류해 설명한 자료다. ↩
-
Future(Future): 나중에 완료될 “한 번의 결과”를 나타내는 타입이다. ↩
-
async/await(async/await): 비동기 작업을 순서대로 읽히는 코드처럼 작성하게 해주는 키워드 조합이다. ↩
-
Stream(Stream): 시간에 따라 여러 값을 순차적으로 전달하는 비동기 시퀀스 타입이다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!