Flutter

[Dart]Primary Constructor 알아보기

Dev.Hansangwook 2026. 10. 13. 22:01

Dart 3.13에서 Primary Constructor가 추가되었다.

기존에도 생성자 파라미터를 필드에 바로 할당하는 this.field 문법을 지원했지만, 필드와 생성자를 각각 선언해야 했다.

 

예를 들어 이런 클래스가 있다고 하자.

class Point {
  final int x;
  final int y;

  Point(this.x, this.y);
}

 

코드는 복잡하지 않다.

 

하지만 x, y라는 상태를 정의하기 위해 필드를 선언하고, 다시 생성자를 선언하고, 생성자에서 같은 이름을 한 번 더 작성해야 한다.

 

Dart 3.13에서는 이를 Primary Constructor로 줄일 수 있다.

class Point(
  final int x,
  final int y,
);

 

클래스 이름 바로 뒤에 생성자 파라미터를 선언한다.

그리고 파라미터에 final이나 var를 붙이면 해당 파라미터와 동일한 이름의 인스턴스 필드가 자동으로 만들어진다.

final과 var

Primary Constructor에서 필드를 생성하려면 파라미터에 final 또는 var를 붙인다.

class User(
  final String name,
  var int age,
);

name은 변경할 수 없는 final 필드가 되고,

user.name = 'Kim'; // Error

age는 변경 가능한 필드가 된다.

user.age = 30;

중요한 점은 아무 modifier도 붙이지 않으면 필드가 생성되지 않는다는 것이다.

class User(String name);

여기서 name은 생성자의 파라미터일 뿐 User의 필드가 아니다.

 

즉,

final user = User('Han');

print(user.name); // Error

처럼 접근할 수 없다.

 

Primary Constructor에서 final, var는 단순히 파라미터의 변경 가능성을 나타내는 것이 아니라 해당 파라미터를 필드로 만들 것인지까지 결정한다.

Named Parameter

Flutter에서는 생성자에 Named Parameter를 사용하는 경우가 많다.

 

Primary Constructor에서도 동일하게 사용할 수 있다.

기존 코드가

class User {
  final String name;
  final int age;

  User({
    required this.name,
    this.age = 0,
  });
}

였다면,

class User({
  required final String name,
  final int age = 0,
});

로 작성할 수 있다.

 

사용하는 쪽은 동일하다.

final user = User(
  name: 'Han',
  age: 29,
);

생성자의 API가 달라지는 것이 아니라 선언하는 방법만 달라진다.

개인적으로 Flutter에서 Primary Constructor를 가장 자주 사용하게 된다면 이런 형태가 아닐까 싶다.

생성자 파라미터를 필드 초기화에 사용할 수도 있다!!

모든 파라미터를 필드로 만들 필요는 없다.

class User(String rawName) {
  final String name = rawName.trim();
}

rawName은 필드가 아니다.

하지만 Primary Constructor의 파라미터이기 때문에 클래스의 필드 초기화에 사용할 수 있다.

기존이라면 다음과 같이 생성자의 initializer list를 사용했을 코드다.

class User {
  final String name;

  User(String rawName) : name = rawName.trim();
}

간단한 값 변환 정도라면 클래스 상단에서 바로 관계를 확인할 수 있다.

생성자 Body가 필요해?

Primary Constructor를 사용하다가 생성자 내부에서 추가 작업이 필요할 수도 있다.

예를 들어 파라미터를 검증하고 싶다고 하자.

기존 생성자라면 이렇게 작성한다.

class User {
  final String name;

  User(this.name) : assert(name.isNotEmpty) {
    print('User created');
  }
}

Primary Constructor에서는 this 블록을 사용한다.

class User(final String name) {
  this : assert(name.isNotEmpty) {
    print('User created');
  }
}

this 뒤에는 기존 생성자처럼 initializer list를 둘 수 있고,

this : assert(name.isNotEmpty);

본문도 작성할 수 있다.

this {
  print('User created');
}

둘 다 필요한 경우 같이 사용하면 된다.

this : assert(name.isNotEmpty) {
  print('User created');
}

Primary Constructor를 사용하기 시작했다고 해서 생성자에 로직이 추가되는 순간 기존 문법으로 다시 되돌아갈 필요는 없다.

const Primary Constructor

const 생성자 역시 지원한다.

 

기존에는

class Point {
  final int x;
  final int y;

  const Point(this.x, this.y);
}

였다면 Primary Constructor에서는 const가 클래스 이름 앞에 위치한다.

class const Point(
  final int x,
  final int y,
);

사용하는 쪽은 기존과 동일하다.

const point = Point(10, 20);

const Primary Constructor의 필드는 기존 const 생성자와 마찬가지로 final이어야 한다.

또한 const 생성자는 생성자 body를 가질 수 없다.

class const Point(final int x) {
  this {
    print(x);
  }
}

이런 코드는 사용할 수 없다.

initializer list만 사용하는 것은 가능하다.

class const Point(final int x) {
  final int doubled;

  this : doubled = x * 2;
}

Named Primary Constructor

Primary Constructor 자체를 Named Constructor로 선언하는 것도 가능하다.

class Point.custom(
  final int x,
  final int y,
);

호출할 때는 기존 Named Constructor와 동일하다.

final point = Point.custom(10, 20);

private constructor도 만들 수 있다.

class User._(final String name);

기존에는 Named Constructor가 클래스 body 내부에 있었다면 Primary Constructor에서는 클래스 이름에 바로 붙는다는 차이가 있다.

상속에서 사용 가능

기존 Dart의 super parameter도 Primary Constructor와 같이 사용할 수 있다.

class Person(final String name);

class Employee(
  super.name,
  final String role,
) extends Person;

기존 문법으로 작성하면 대략 다음과 같다.

class Person {
  final String name;

  Person(this.name);
}

class Employee extends Person {
  final String role;

  Employee(
    String name,
    this.role,
  ) : super(name);
}

상속 구조에서 단순히 값을 전달하기 위한 생성자 코드가 상당히 줄어든다.

Enum에서 사용 가능

Enhanced Enum에서도 Primary Constructor를 사용할 수 있다.

기존에는

enum Color {
  red('#FF0000'),
  green('#00FF00'),
  blue('#0000FF');

  const Color(this.hex);

  final String hex;
}

처럼 작성했다면,

enum Color(final String hex) {
  red('#FF0000'),
  green('#00FF00'),
  blue('#0000FF');
}

로 줄일 수 있다.

Enum의 Primary Constructor는 암시적으로 const이기 때문에 별도로 const를 작성할 필요도 없다.

이런 단순한 데이터 형태에서 Primary Constructor의 장점이 가장 잘 보이는 것 같다.

마이그레이션 할까?

Primary Constructor가 기존 생성자를 대체하는 것은 아니다.

 

Dart에서도 여전히 기존처럼 클래스 body 안에 생성자를 선언할 수 있다.

class User {
  final String name;

  User(this.name);
}

Primary Constructor가 있는 클래스에서 다른 generative constructor를 선언하려면 Primary Constructor로 redirect해야 하는 등의 제약도 있다. 또 클래스 이름 뒤에 타입 파라미터, extends, with, implements, 생성자 파라미터까지 붙기 시작하면 오히려 클래스 선언부가 길어질 수도 있다.

 

따라서

class User({
  required final String id,
  required final String name,
  final int age = 0,
});

처럼 생성자의 주된 역할이 전달받은 값을 필드에 저장하는 것이라면 Primary Constructor가 잘 맞는다.

반대로 생성자가 여러 개 존재하거나 초기화 과정이 복잡하다면 기존 생성자 문법이 더 읽기 쉬울 수도 있다.

마치며

Primary Constructor로 더욱 간결하게 class 선언이 가능해졌다.

 

특히 Flutter에서는 Entity, DTO, State, Widget 등 생성자로 값을 전달받아 그대로 필드에 저장하는 클래스를 많이 작성한다.

그런 코드에서는 꽤 많은 boilerplate를 줄일 수 있을 것 같다.

 

다만 짧다는 이유만으로 전부 Primary Constructor로 바꾸기보다는, 클래스 선언부만 읽었을 때 생성자와 상태가 더 명확하게 보이는 경우에 사용하는 것이 적절해 보인다.

'Flutter' 카테고리의 다른 글

[Flutter]Riverpod 간단 압축 정리  (0) 2026.08.13