RƏSƏDXANA

Həftə 9 · 17/23

Vəziyyət maşınları: mümkün olmayanı yazıla bilməz et

6 dəqiqəlik oxu

Qapı ya açıqdır, ya bağlı, ya kilidli. «Həm açıq, həm kilidli» deyə bir hal yoxdur — amma üç ayrı düymə qoysan, insanlar onu yarada bilər.

Hər hal üçün ayrı boolean qoymaq mümkün olmayan kombinasiyalar yaradır. Vəziyyətləri adlandırıb yalnız icazəli keçidləri yazsan, mənasız hal ümumiyyətlə yazıla bilmir.

// three switches: 8 combinations, 4 of them nonsense
isLoading, isError, hasData

// one state: only what can actually happen
{ status: "idle" | "loading" | "error" | "done" }
Yuxarıdakı «yüklənir və xəta» halını yazmağa imkan verir. Aşağıdakı vermir.

Çox deyilən söz: Hər hal üçün bir boolean: isLoading, isError, isDone…

Beş boolean 32 kombinasiya deməkdir və onların çoxu mənasızdır: eyni anda həm yüklənir, həm xəta. Seniorlar əvvəl vəziyyətləri və keçidləri adlandırır, sonra AI-dan yalnız icazə verilən keçidləri yazmasını istəyir. Belə model tiplə qorunur, cədvəllə test olunur və kağızda çəkilə bilir.

Sənin kulvarında

Form və ödəniş axını

Veb-də vəziyyət maşını ən çox çoxaddımlı formlarda və ödənişdə lazım olur: boş, doldurulur, göndərilir, rədd edildi, təsdiqləndi. «Göndərilir və rədd edildi» eyni anda ola bilməz.

Fikir eynidir, rəqəmlər və alətlər fərqli. Seçimin brauzerində yadda qalır və bütün dərslərə tətbiq olunur.

UI-dan əvvəl vəziyyətləri sadala

Kağızda vəziyyətləri və onları dəyişən hadisələri çək. Diaqramda olmayan keçid kodda da olmamalıdır.

Sən bunu istə

idle --pick--> ready --upload--> uploading --ok--> done
                                 uploading --fail--> failed --retry--> uploading

Discriminated union

Hər vəziyyətin öz etiketi və yalnız özünə aid məlumatı var. «Yüklənir» halında error yoxdur, «uğursuz» halında url yoxdur — tip buna icazə vermir.

AI belə yazır

const [isLoading, setLoading] = useState(false);
const [isError, setError] = useState(false);
const [data, setData] = useState(null);

Sən bunu istə

type Upload =
  | { status: "idle" }
  | { status: "uploading"; progress: number }
  | { status: "failed"; error: string }
  | { status: "done"; url: string };

Saf keçid funksiyası, cədvəl testi

(vəziyyət, hadisə) → yeni vəziyyət. Yan təsir yoxdur, ona görə hər icazəli və qadağan keçid bir cədvəl sətri ilə test olunur.

Sən bunu istə

function next(s: Upload, e: UploadEvent): Upload {
  if (s.status === "failed" && e.type === "retry") return { status: "uploading", progress: 0 };
  if (s.status === "uploading" && e.type === "ok") return { status: "done", url: e.url };
  // ...
  return s;   // anything not listed is ignored
}

Diff oxuyanda soruş: Komponentə bax: statusu neçə boolean daşıyır? Üç və daha çoxdursa, mümkün olmayan hal artıq oradadır.

Terminlər

finite state machine
Sonlu sayda adlı vəziyyət və onlar arasında icazə verilən keçidlər.
transition
Bir hadisənin bir vəziyyəti digərinə çevirməsi. Siyahıda olmayan keçid qadağandır.
discriminated union
TypeScript tipi: hər vəziyyətin öz status etiketi və yalnız özünə aid məlumatı var.
guard
Keçidin şərti: «yalnız fayl seçilibsə yükləməyə keç».
impossible state
Kodun təmsil edə bildiyi, amma real dünyada olmamalı olan kombinasiya.
pure reducer
(vəziyyət, hadisə) → yeni vəziyyət funksiyası, yan təsirsiz — ona görə cədvəllə test olunur.

İndi sən yoxla

1Dizayn problemi nədir?

const [isLoading, setLoading] = useState(false);
const [isError, setError] = useState(false);
const [data, setData] = useState(null);

2«Yenidən cəhd et» düyməsi əlavə olunur. Hansı keçid düzgündür?

type Upload =
  | { status: "idle" }
  | { status: "uploading"; progress: number }
  | { status: "failed"; error: string }
  | { status: "done"; url: string };

Hazır prompt

İngiliscədir, çünki terminlər ingiliscədir. Tərcümə etsən də terminləri saxla.

prompt
Model <feature> as a finite state machine before writing any UI.

- List every state by name, and the data that exists only in that state.
- List every event and the transition it causes; anything not listed is forbidden.
- Mark guards (conditions on a transition) and side effects (what runs on entry).
- Express it in TypeScript as a discriminated union plus a pure
  transition function.
- Write a table test that walks every allowed transition and asserts
  that at least one forbidden one is refused.

Öz kodunda

  1. 1Statusu üç və ya daha çox boolean ilə saxlayan bir komponent tap.
  2. 2Hansı kombinasiyaların həqiqətən mümkün olduğunu yaz və vəziyyət diaqramını çək.
  3. 3AI-a onu discriminated union + reducer kimi yenidən yazdır və keçid cədvəli testini əlavə et.

Bunu görəndə bitib: Mümkün olmayan kombinasiya artıq tip səviyyəsində yazıla bilmir və keçid cədvəli testi keçir.

Daha dərinə

Oxumaq üçün