Sparround

Immutable state: freezed, Equatable, == və rebuild

Immutability Flutter-də estetik seçim deyil — bütün optimizasiya mexanizmlərinin şərtidir. Səbəb sadədir: Flutter və state management library-ləri "nəyin dəyişdiyini" == müqayisəsi ilə müəyyən edir.

Bu mexanizmlərin hər biri ==-dən asılıdır:

  • InheritedWidget.updateShouldNotify — abunəçilərin oyanması.
  • ValueNotifiervalue setter-i == ilə müqayisə edir.
  • Provider-də Selectorcontext.select — seçilən dəyər dəyişməyibsə rebuild yoxdur.
  • Riverpod-da select və provider-lərin yenidən hesablanması.
  • Bloc-un təkrar state qaydası — state == nextState olduqda dəyişiklik baş vermir.
  • BlocSelector — sənəd seçilən dəyərin immutable olmasını tələb edir.

Mutable obyektdə isə eyni instansiya dəyişdirilir: == fərq görmür, filtr işləmir və nəticə iki uc nöqtədən biri olur — ya UI heç vaxt yenilənmir, ya hər şey həmişə rebuild olunur.

AlətNə verirNəyə diqqət
Əl ilə `==`/`hashCode`Asılılıq yoxdurYeni sahə əlavə edildikdə yaddan çıxır
`package:equatable``props` siyahısı ilə qısa `==``props`-a yeni sahəni əlavə etmək yaddan çıxarsa, müqayisə yalan danışır
`package:freezed``==`, `hashCode`, `copyWith`, sealed union-lar kod generasiyası ilə`build_runner` build vaxtı; generated fayllar repo-da idarə olunmalıdır
Dart `record`-larıHazır dəyər semantikası, kod generasiyası olmadanAd verilmiş tip yoxdur; böyük modellər üçün uyğun deyil

freezed haqqında vacib yenilik. Paketin sənədi when/map metodlarını artıq legacy kimi işarələyir və izahı belədir: Dart 3-də dilin özündə sealed siniflər üzərində pattern matching var, ona görə Freezed-in generasiya etdiyi metodlara etibar etmək lazım deyil. Tövsiyə olunan yol — switch ifadələri və if-case konstruksiyaları.

copyWith isə tam şəkildə generasiya olunmağa davam edir və dərin (nested) formada da işləyir.

Müsahibədə bu detalı bilmək faydalıdır: when/map istifadə edən kod işləyir, lakin yeni kodda Dart 3 pattern matching üstün tutulur.

dart
// 1) Equatable: props siyahısı — yeni sahə əlavə edildikdə buraya da yazılmalıdır.
class CartState extends Equatable {
  const CartState({this.items = const [], this.promo});
  final List<Item> items;
  final String? promo;

  CartState copyWith({List<Item>? items, String? promo}) =>
      CartState(items: items ?? this.items, promo: promo ?? this.promo);

  @override
  List<Object?> get props => [items, promo];
}

// 2) freezed: ==, hashCode, copyWith generasiya olunur; sealed union-lar dəstəklənir.
@freezed
sealed class CheckoutState with _$CheckoutState {
  const factory CheckoutState.idle() = CheckoutIdle;
  const factory CheckoutState.submitting() = CheckoutSubmitting;
  const factory CheckoutState.success(Receipt receipt) = CheckoutSuccess;
  const factory CheckoutState.failure(String message) = CheckoutFailure;
}

// Dart 3 pattern matching — freezed sənədinin tövsiyə etdiyi yol
// (when/map artıq legacy sayılır).
Widget build(BuildContext context) => switch (state) {
      CheckoutIdle() => const CheckoutForm(),
      CheckoutSubmitting() => const CircularProgressIndicator(),
      CheckoutSuccess(:final receipt) => ReceiptView(receipt: receipt),
      CheckoutFailure(:final message) => ErrorView(message: message),
    };

// 3) record: kiçik, adsız dəyərlər üçün — kod generasiyası lazım deyil.
typedef SearchFilter = ({String query, int page});
final filter = (query: 'telefon', page: 1); // == dəyər üzrə işləyir

Immutable state üç üsulla.

Immutable state-in praktik qiyməti də var: hər dəyişiklik yeni obyekt yaradır. Böyük siyahılarda hər emit-də [...items, item] yazmaq O(n) kopyalama deməkdir. Kiçik siyahılarda bu əhəmiyyətsizdir; minlərlə element olduqda isə səhifələmə, ListView.builder və dəyişiklik sahəsini məhdudlaşdıran struktur (məsələn siyahını id → element şəklində saxlamaq) lazımdır. Müsahibədə bu balansı qeyd etmək "immutable həmişə yaxşıdır" cavabından yetkindir.

📚 Mənbələr və sənədlər