Flutter와 Dart 3: 레코드, 패턴 매칭, 고급 면접 질문 완벽 가이드

Dart 3의 레코드, 패턴 매칭, 실드 클래스를 Flutter 코드 예제와 함께 상세히 설명합니다. 완전 매칭, 상태 모델링, 기술 면접 대비 핵심 질문을 다룹니다.

Dart 3 records, patterns and sealed classes for Flutter development

Dart 3의 레코드, 패턴 매칭, 실드 클래스는 2026년 모던 Flutter 개발의 기반을 형성합니다. Dart 3.0에서 도입되고 Dart 3.11까지 개선된 이 기능들은 대수적 데이터 타입, 완전 패턴 매칭, 그리고 런타임 이전에 버그 범주 전체를 감지하는 컴파일 타임 안전성을 제공합니다.

핵심 요약

레코드는 클래스 없이 여러 값을 묶습니다. 패턴은 이를 한 줄로 분해합니다. 실드 클래스는 컴파일 타임에 switch의 완전성을 보장합니다. 이 세 가지가 결합되면 기존의 보일러플레이트가 많은 상태 모델링 코드 대부분을 대체할 수 있습니다.

Dart 3 레코드 이해: 경량 데이터 집약

레코드는 Dart의 오랜 과제인 '전용 클래스를 생성하지 않고 함수에서 여러 값을 반환하는 문제'를 해결합니다. 레코드는 익명이며 불변인 집약 타입입니다.

user_repository.dartdart
// Returning multiple values with a positional record
(String, int) parseUserInput(String raw) {
  final parts = raw.split(':');
  return (parts[0].trim(), int.parse(parts[1]));
}

// Named fields improve readability for complex returns
({String name, String email, bool isVerified}) fetchUserProfile(String id) {
  // Simulated database lookup
  return (
    name: 'Alice Chen',
    email: 'alice@example.com',
    isVerified: true,
  );
}

레코드는 구조적으로 타입이 지정됩니다. 동일한 필드 타입과 이름을 가진 두 레코드는 같은 타입입니다. 이를 통해 Dart 3 이전에 Flutter 개발자들이 의존했던 Tuple2, Pair 또는 유사한 유틸리티 클래스가 필요 없어집니다.

records_equality.dartdart
// Structural equality — no need for custom == operator
void main() {
  final a = (1, 'hello');
  final b = (1, 'hello');
  print(a == b); // true — records compare by value

  // Named fields also support equality
  final profile1 = (name: 'Alice', role: 'admin');
  final profile2 = (name: 'Alice', role: 'admin');
  print(profile1 == profile2); // true
}

레코드는 함수 반환값, 로컬 데이터 집약, 맵 키에 가장 적합합니다. 메서드와 유효성 검사가 필요한 도메인 모델에는 여전히 클래스가 올바른 선택입니다.

Dart 패턴 매칭: 분해와 제어 흐름

Dart 3의 패턴 매칭은 단순한 타입 체크를 넘어섭니다. 패턴은 값을 분해하고, 변수를 바인딩하며, 제어 흐름에 참여합니다. 이 모든 것을 단일 표현식에서 수행할 수 있습니다.

pattern_basics.dartdart
// Destructuring a record with pattern matching
void main() {
  final (name, email, isVerified) = fetchUserProfile('u-123');
  // name, email, isVerified are now local variables

  // List patterns with rest operator
  final scores = [98, 87, 92, 76, 84];
  final [first, second, ...remaining] = scores;
  print('Top two: $first, $second'); // 98, 87
  print('Others: $remaining');        // [92, 76, 84]
}

switch 표현식은 패턴과 결합하여 장황한 if-else 체인을 간결하고 완전한 매칭으로 대체합니다:

pattern_switch.dartdart
// Switch expression with guard clauses
String classifyScore(int score) => switch (score) {
  >= 90 => 'Excellent',
  >= 80 => 'Good',
  >= 70 => 'Average',
  >= 60 => 'Below Average',
  _ => 'Needs Improvement',
};

// Object pattern matching with type checking
String describeValue(Object value) => switch (value) {
  int n when n < 0   => 'Negative integer: $n',
  int n              => 'Positive integer: $n',
  String s when s.isEmpty => 'Empty string',
  String s           => 'String of length ${s.length}',
  List l when l.isEmpty  => 'Empty list',
  List l             => 'List with ${l.length} elements',
  _                  => 'Unknown type',
};

가드 절(when 키워드)은 패턴에 조건 로직을 추가하면서도 중첩을 방지합니다. 이로 인해 switch 표현식이 평탄하고 가독성이 높아집니다.

실드 클래스: 완전한 상태 모델링

실드 클래스는 서브타입 계층을 동일 파일 내로 제한하여, Dart 컴파일러가 switch에서 모든 서브타입이 처리되었는지 검증할 수 있게 합니다. 이것이 타입 안전한 상태 관리의 핵심 요소입니다.

auth_state.dartdart
// Sealed class defining all possible authentication states
sealed class AuthState {}

class AuthInitial extends AuthState {}

class AuthLoading extends AuthState {}

class AuthAuthenticated extends AuthState {
  final String userId;
  final String displayName;
  AuthAuthenticated({required this.userId, required this.displayName});
}

class AuthError extends AuthState {
  final String message;
  final int? statusCode;
  AuthError({required this.message, this.statusCode});
}

컴파일러는 완전성을 강제합니다. 모든 서브클래스가 처리되어야 합니다:

auth_widget.dartdart
// Exhaustive switch — compiler error if a case is missing
Widget buildAuthUI(AuthState state) => switch (state) {
  AuthInitial()       => const LoginPrompt(),
  AuthLoading()       => const CircularProgressIndicator(),
  AuthAuthenticated(
    displayName: final name
  )                   => Text('Welcome, $name'),
  AuthError(
    message: final msg,
    statusCode: final code,
  )                   => ErrorBanner(
    message: msg,
    code: code,
  ),
};

새로운 AuthState 서브클래스(예: AuthSessionExpired)를 추가하면 해당 AuthState를 처리하는 모든 switch 표현식에서 즉시 컴파일 에러가 발생합니다. 이를 통해 문자열 기반이나 enum 기반 상태 관리를 사용하는 Flutter 앱에서 흔한 버그 원인인 '상태 처리 누락'이 제거됩니다.

Flutter 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

Flutter에서 레코드, 패턴, 실드 클래스의 통합 활용

이 세 가지 기능이 함께 작동할 때 진정한 힘이 발휘됩니다. Flutter API 호출의 실용적 패턴으로, 결과 타입에 실드 클래스를, 구조화된 데이터에 레코드를 결합하는 방법이 있습니다:

api_result.dartdart
// Generic sealed result type for API operations
sealed class ApiResult<T> {}

class ApiSuccess<T> extends ApiResult<T> {
  final T data;
  final ({int statusCode, Map<String, String> headers}) metadata;
  ApiSuccess(this.data, {required this.metadata});
}

class ApiFailure<T> extends ApiResult<T> {
  final String error;
  final int? statusCode;
  ApiFailure(this.error, {this.statusCode});
}
product_screen.dartdart
// Consuming the sealed result with pattern matching
Widget buildProductList(ApiResult<List<Product>> result) => switch (result) {
  ApiSuccess(
    data: final products,
    metadata: (statusCode: 200, headers: _),
  ) when products.isNotEmpty => ListView.builder(
    itemCount: products.length,
    itemBuilder: (_, i) => ProductCard(products[i]),
  ),
  ApiSuccess(data: final products) when products.isEmpty =>
    const EmptyState(message: 'No products found'),
  ApiFailure(statusCode: 401) =>
    const AuthExpiredBanner(),
  ApiFailure(error: final msg) =>
    ErrorDisplay(message: msg),
};

이 패턴을 통해 null 체크, 타입 캐스트, 런타임 어설션이 불필요해집니다. 모든 가능한 상태가 컴파일 타임에 고려됩니다.

Dart 3 기능 관련 고급 면접 질문

2026년 Flutter 포지션 기술 면접에서는 Dart 3 언어 기능에 대한 이해도를 테스트하는 질문이 증가하고 있습니다. 아래는 문법을 넘어 아키텍처적 사고를 검증하는 질문들입니다.

Q: 데이터 모델링에서 레코드와 클래스는 어떻게 다릅니까?

레코드는 구조적으로 타입이 지정되고, 불변이며, 익명입니다. 동일한 필드 타입을 가진 두 레코드는 같은 타입입니다. 클래스는 명목적으로 타입이 지정됩니다. 동일한 필드를 가진 두 클래스는 다른 타입입니다. 레코드는 함수 반환값, 로컬 집약, 맵 키에 적합합니다. 클래스는 메서드, 유효성 검사, 아이덴티티가 필요한 도메인 모델에 적합합니다.

Q: enum보다 실드 클래스 계층이 선호되는 경우는 언제입니까?

enum은 배리언트별 데이터를 가질 수 없습니다. 실드 클래스는 각 서브타입이 서로 다른 필드를 보유할 수 있습니다. 예를 들어, AuthAuthenticateduserId를, AuthErrormessagestatusCode를 가집니다. enum으로는 이를 모델링할 수 없습니다. 실드 클래스는 배리언트 이름뿐 아니라 필드에 대한 깊은 패턴 매칭도 지원합니다.

Q: 실드 클래스에 새 서브타입을 추가하면 컴파일 타임에 무엇이 발생합니까?

Dart 분석기가 해당 실드 타입을 처리하는 모든 switch 표현식 또는 문을 즉시 비완전으로 표시합니다. 새로운 케이스가 모든 곳에서 처리될 때까지 코드가 컴파일되지 않습니다. 이 컴파일 타임 보장이 완전성 검사를 제공하지 않는 추상 클래스에 비한 주요 장점입니다.

Q: 패턴에서의 _와 와일드카드 변수로서의 _(Dart 3.7 이후)의 차이를 설명하십시오.

Dart 3.7 이전에는 _가 패턴 내에서만 와일드카드로 작동했습니다. var [_, _, third] = list는 패턴 _가 바인딩하지 않기 때문에 동작했습니다. 패턴 외부에서 _는 일반 변수명이었습니다. Dart 3.7에서 이것이 통일되어, _는 함수 파라미터와 로컬 변수를 포함한 모든 곳에서 비바인딩이 되었습니다. 동일 스코프 내의 여러 _ 선언이 더 이상 충돌하지 않습니다.

Q: 패턴 매칭은 실제로 null 안전성을 어떻게 개선합니까?

패턴은 if-case를 통한 간결한 null 체크를 가능하게 합니다:

null_pattern.dartdart
// If-case for null-safe extraction
void processUser(Map<String, dynamic> json) {
  if (json case {'name': String name, 'age': int age}) {
    // name and age are non-nullable here
    print('$name is $age years old');
  } else {
    print('Invalid user data');
  }
}

패턴은 맵 구조 검증, 타입 체크, 비null 변수 바인딩을 동시에 수행합니다. 패턴이 없다면 중첩된 null 체크와 수동 타입 캐스트가 필요합니다.

마이그레이션 전략: 기존 Flutter 앱에 Dart 3 기능 도입

기존 Flutter 코드베이스에 Dart 3 패턴을 마이그레이션하는 작업은 점진적으로 수행할 수 있습니다. 가장 높은 가치의 변경부터 시작합니다.

단계 1 — Tuple/Pair 타입을 레코드로 교체. Tuple2, Pair, 또는 반환값 번들링 목적으로만 사용되는 커스텀 2필드 클래스를 검색합니다. 레코드로 교체하면 즉시 코드량이 감소합니다.

단계 2 — if-else 타입 체크를 switch 표현식으로 변환. if (value is TypeA) ... else if (value is TypeB) 체인은 모두 패턴 매칭을 사용하는 switch 표현식이 됩니다. 이는 특히 위젯 빌딩과 상태 처리에 적용됩니다.

단계 3 — 상태 계층 실드화. BLoC 상태, Riverpod notifier 상태, 커스텀 상태 클래스에 sealed 수정자를 추가합니다. 컴파일러가 상태 처리가 불완전한 모든 위치를 식별합니다.

단계 4 — switch에서 가드 절 채택. case 블록 내의 if 체크를 when 가드로 교체하여 더 평탄하고 읽기 쉬운 제어 흐름을 구현합니다.

각 단계는 독립적으로 가치가 있으며 코드베이스 전체를 한 번에 수정할 필요가 없습니다.

Dart 3.10 도트 축약

Dart 3.10(2025년 11월)에서는 도트 축약이 도입되어, 컨텍스트에서 타입이 알려진 경우 EnumType.value 대신 .value로 작성할 수 있게 되었습니다. 이는 패턴 매칭과 잘 어울리며, enum 유사 타입을 처리하는 switch 표현식의 시각적 노이즈를 줄여줍니다.

레코드는 클래스가 아닙니다

레코드는 메서드, 커스텀 생성자, 상속을 가질 수 없습니다. 레코드 타입에 동작을 추가하려는 시도는 클래스 또는 실드 클래스 계층이 더 적합하다는 신호입니다. 레코드는 동작 없는 데이터 집약에만 사용해야 합니다.

결론

  • 레코드는 다중 값 반환의 보일러플레이트를 제거합니다. Tuple, Pair, 단일 목적 클래스 대신 사용합니다
  • 패턴 매칭은 if-else 타입 체크 체인을 완전하고 컴파일러 검증된 switch 표현식으로 대체합니다
  • 실드 클래스는 모든 상태 배리언트가 처리됨을 보장하여 런타임 버그를 컴파일 타임 에러로 전환합니다
  • 가드 절(when)은 switch 표현식을 평탄하게 유지하고 case 블록 내의 중첩 조건을 제거합니다
  • Dart 3.7 와일드카드 변수(_)는 패턴의 비바인딩 동작을 일반 파라미터 및 로컬 변수와 통일합니다
  • 마이그레이션은 점진적으로 수행 가능합니다: 함수 반환값에 레코드를 도입하고, 타입 체크를 switch로 변환하며, 상태 계층을 실드합니다
  • 이 기능들은 Flutter 기술 면접에서 적극적으로 테스트됩니다. 문법뿐 아니라 '왜'에 대한 이해가 중요합니다

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

태그

#flutter
#dart-3
#records
#patterns
#sealed-classes
#interview

공유

관련 기사