본문 바로가기

Personal Posting/Flutter

Dart 3.3의 Extension Types: 쉽게 이해하는 개념과 활용법

Dart 3.3부터 도입된 Extension Types(확장 타입)은 기존 타입에 새로운 타입 레이어를 씌워, 더 안전하고 명확하게 코드를 작성할 수 있게 해주는 기능이다. 이번 포스팅에서는 Dart Extension Types의 개념, 장점, 사용법, 그리고 실전 예시를 정리해보도록 한다.

 

1. Extension Types란?

  • 정의: Extension Type은 기존 타입(int, String, List 등)에 컴파일 타임에서만 존재하는 새로운 타입을 덧씌우는 기능이다.
  • 특징: 런타임에는 실제로 래핑(wrapping)된 타입(Representation Type)으로 동작하며, 별도의 메모리 비용 없이 타입 안정성과 코드 가독성을 높여준다.
  • 비교: 기존의 Wrapper Class(예: class MyId { final int id; ... })는 객체를 생성하고 메모리를 차지하지만, Extension Type은 이런 오버헤드가 없다.

<공식 홈페이지 매뉴얼>

https://dart.dev/language/extension-types?source=post_page-----5dfa73e4b009---------------------------------------

2. Extension Types의 장점

  • 성능 최적화: 런타임 오버헤드가 없어 대량의 데이터나 빈번한 객체 생성 상황에서 유리하다.
  • 타입 안전성: 기존 타입과 확장 타입을 명확히 구분해, 잘못된 타입 사용을 컴파일 타임에 잡아낼 수 있다.
  • 코드 가독성: 의미 있는 이름을 부여해 코드의 목적과 데이터를 더 명확하게 표현할 수 있다.
  • 플랫폼/언어 간 연동: JS, C++ 등 네이티브 코드와의 상호 운용성(interop)이 좋아진다.

3. 기본 사용 방법

1) 선언과 생성

extension type MyId(int id) {}

void main() {
  final id = MyId(1);
  print(id); // 1
  print(id.runtimeType); // int
}

MyId는 int를 감싸는 확장 타입이며, 런타임에는 int로 동작한다.

 

2) 타입 변환

MyId myId = MyId(10);
int number = myId; // ❌ 컴파일 에러
int number2 = myId as int; // ✅
myId = number2 as MyId; // ✅

확장 타입과 원본 타입은 직접 대입 불가. as를 통해 변환해야 한다.

 

3) 연산자 및 메서드 추가

extension type MyId(int id) {
  bool isBiggerThan(MyId other) => id > other.id;
}

void main() {
  MyId a = MyId(100);
  MyId b = MyId(200);
  print(a.isBiggerThan(b)); // false
}

기존 int의 연산자(+, -, > 등)는 사용할 수 없고, 직접 정의해야 한다.

 

4. 확장 타입의 제약과 활용

1) 타입 제한

  • 확장 타입과 원본 타입은 별개로 취급되어, 실수로 타입이 섞이는 것을 방지한다.
  • 예를 들어, 사용자 ID, 주문 번호 등 의미가 다른 int 값을 명확히 구분할 수 있다.

2) 제네릭 사용

extension type MyList<T>(List<T> elements) {
  void add(T value) => elements.add(value);
}

void main() {
  MyList<int> list = MyList([1, 2]);
  list.add(3);
  print(list); // [1, 2, 3]
}

 

3) 생성자 다양화

extension type Password._(String value) {
  Password(this.value) {
    if (value.length < 8) throw Exception('비밀번호는 8자 이상이어야 합니다.');
  }
  Password.random() : value = _generateRandomPassword();
  static String _generateRandomPassword() => '랜덤비밀번호';
  bool get isValid => value.length >= 8;
}

기본 생성자 외에, 네임드 생성자, private 생성자 등도 정의할 수 있다.

 

5. implements를 활용한 안전한 별칭(Safe Aliases)

확장 타입에 implements를 사용하면 원본 타입의 모든 기능을 사용할 수 있다.

extension type Height(double _) implements double {}
extension type Weight(double _) implements double {}

double calculateBmi(Height height, Weight weight) => weight / (height * height);

void main() {
  var height = Height(1.75);
  var weight = Weight(65);
  print(calculateBmi(height, weight)); // 21.22...
}

Height, Weight 모두 double을 구현하지만, 서로 다른 타입이므로 잘못된 인자 전달을 컴파일 타임에 막을 수 있다.

6. 활용 예시

1) 네이티브/JS 연동

  • JS, C++ 등 외부 라이브러리와 연동할 때, 별도의 래퍼 클래스를 만들지 않고도 안전하게 타입을 감쌀 수 있다.

2) JSON 데이터 구조화

typedef UserInfo = ({String email, String password});
extension type User(UserInfo info) {
  void printInfo() => print("Email: ${info.email}, Password: ${info.password}");
}

void main() {
  final user = User((email: 'test@gmail.com', password: '1234'));
  user.printInfo();
}

 

3) 테스트용 Mock 클래스

abstract final class Repository {
  String getToken();
}
final class MyRepository implements Repository {
  @override String getToken() => 'token';
}
extension type MockRepository(Repository repository) implements Repository {
  String getToken() => 'Testing';
}

 

7. 앞선 포스팅에서 작성했던 Extension Method와의 차이점

구분 Extension Method Extension Type
목적 기존 타입에 메서드 추가 기존 타입에 새로운 타입 레이어 부여
런타임 영향 없음 없음(컴파일 타임에만 존재)
타입 안정성 기존 타입 그대로 사용 타입 구분 및 안전성 강화
사용 예시 String 확장 메서드 의미 있는 타입 분리(예: UserId, OrderId 등)

 

8. 주의사항 및 한계

  • 확장 타입은 컴파일 타임에만 존재하며, 런타임에는 원본 타입으로 동작한다.
  • 기존 타입의 모든 기능을 사용하려면 implements를 사용해야 한다.
  • 기존 타입과 확장 타입을 직접 대입할 수 없고, as를 통해 변환해야 한다.
  • Extension Type은 복잡한 타입 구조를 안전하게 관리하고, 네이티브 연동, 대규모 데이터 처리 등에서 성능과 유지보수성을 크게 향상시킬 수 있다.

9. 정리

  • Extension Type은 기존 타입에 의미 있는 이름과 타입 안전성을 더하는 Dart 3.3의 새로운 기능이다.
  • Wrapper Class의 런타임 오버헤드 없이, 타입 구분과 코드 가독성을 높일 수 있다.
  • 네이티브 연동, 대규모 데이터 처리, 의미 있는 타입 분리 등 다양한 상황에서 활용할 수 있다.
  • 실제 프로젝트에서 UserId, ProductId, Money 등 의미가 분명한 값에 Extension Type을 활용하면, 실수로 잘못된 값을 사용하는 버그를 컴파일 타임에 미리 막을 수 있다.

 

아래 아티클을 읽고 그대로 번역한 것이 아닌 새롭게 정리한 내용입니다.

https://medium.com/flutter-community/what-can-i-do-with-extension-types-in-dart-5dfa73e4b009