본문으로 건너뛰기

불변성(Immutability)의 중요성

불변성(Immutability)이란?

불변성은 객체의 모든 필드가 final 또는 late final인 경우를 의미합니다. 이러한 필드들은 생성 시에만 설정되며, 이후에는 변경할 수 없습니다.

불변성은 여러 가지 이유에서 유용합니다.

  • reference equality(레퍼런스 동등성)이 아닌 value equality(값의 동등성)을 가져옵니다.
  • 코드의 지역적인 이해가 용이해집니다.
    • 다른 곳에 있는 코드가 레퍼런스에 접근하여 객체를 변경하는 것을 방지할 수 있습니다
  • 비동기 및 병렬 작업에 대해 이해하기 쉬워집니다.
    • 작업 사이에 다른 코드가 객체를 변경할 수 없습니다.
  • API의 안전성이 보장됩니다.
    • 메서드에 전달한 객체는 호출자 또는 피호출자에 의해 변경될 수 없습니다.

copyWith 메서드는 기존 객체에서 몇 가지 속성만 변경된 새로운 객체를 생성할 때, 코드의 복잡도를 줄이는 데 도움이 될 수 있습니다.

복사 작업은 예상보다 더 효율적입니다. 왜냐하면 Dart는 변경되지 않은 하위 객체에 대한 참조를 재사용할 수 있기 때문입니다.

위험

객체가 깊은 불변성(deeply immutable)을 가지고 있는지 확인하십시오. 그렇지 않으면 당신은 깊은 복사 메커니즘을 구현해야 할 수 있습니다.

최적의 방법

불변 상태를 만들기 위해 다양한 패키지를 이용할 수 있습니다.

immutable한 객체의 경우

immutable한 컬렉션의 경우 (Map, Set, List)

freezed 사용을 높이 권장합니다. 이 패키지에는 불변의 객체를 만드는 것 이외에도 몇 가지 멋진 추가 기능들이 있습니다.

  • copyWith 메서드
  • 깊은 복사 (중첩된 freezed 객체의 copyWith)
  • Union 타입
  • Union 매핑 함수

불변 상태를 다루기 위해 code generation을 사용해야 할 필요는 없지만, 이를 사용하면 작업이 더 쉬워집니다.

위험

빌트인 컬렉션을 사용하려면, 업데이트할 때 컬렉션을 복사하는 규칙을 강제해야 합니다. 컬렉션을 복사하지 않으면 riverpod는 객체에 대한 레퍼런스가 변경되었는지를 기반으로 새 상태를 전달할지 결정합니다. 객체를 변경하는 메서드를 단순히 호출한다면, 레퍼런스는 이전과 동일하기 때문에 객체가 변경되지 않을 수 있습니다.

불변 상태 사용하기

불변 상태는 StateNotifierStateNotifierProvider를 결합하여 사용하는 것이 가장 적합합니다. StateNotifier를 사용하면 상태를 '변경'할 수 있는 인터페이스를 노출할 수 있습니다. StateNotifier를 확장하는 클래스 밖에서는 상태를 변경할 수 없으므로, 역할 분리를 강제하고 비즈니스 로직을 UI 바깥에 유지할 수 있습니다.

다음은 앱 테마를 변경하는 불변 상태의 설정 클래스 예제입니다.

final themeProvider = StateNotifierProvider((ref) => ThemeNotifier());

class ThemeNotifier extends StateNotifier<ThemeSettings> {
ThemeNotifier(): super(
ThemeSettings(
mode: ThemeMode.system,
primaryColor: Colors.blue,
));

void toggle() {
state = state.copyWith(mode: state.mode.toggle);
}
void setDarkTheme() {
state = state.copyWith(mode: ThemeMode.dark);
}
void setLightTheme() {
state = state.copyWith(mode: ThemeMode.light);
}
void setSystemTheme() {
state = state.copyWith(mode: ThemeMode.system);
}
void setPrimaryColor(Color color) {
state = state.copyWith(primaryColor: color);
}

}


class ThemeSettings with _$ThemeSettings {
const factory ThemeSettings({ThemeMode mode, Color primaryColor}) = _ThemeSettings;
}

extension ToggleTheme on ThemeMode {
ThemeMode get toggle {
switch (this){
case ThemeMode.dark:
return ThemeMode.light;
case ThemeMode.light:
return ThemeMode.dark;
case ThemeMode.system:
return ThemeMode.system;
}
}
}

이 코드를 적용하려면, 'freezed_annotation'을 import하고 part 지시문을 추가한 후, build_runner를 실행하여 freezed 클래스를 생성해야 하는 것을 기억하세요!