@wordpress/data ve TypeScript ile Sonlu Durum Makineleri
Yayınlanan: 2021-09-16Diyelim ki uygulamamızda bir login ekranı tasarlamak istiyoruz. Kullanıcı onu ilk gördüğünde boş bir form çıkıyor. Kullanıcı alanları doldurur ve hepsi ayarlandıktan sonra, kimlik bilgilerini doğrulamak ve oturum açmak için oturum açma düğmesine basar. Doğrulama başarılı olursa, bir sonraki ekrana geçerler. Ancak olmazsa, bir hata mesajı verilir ve tekrar denemeleri istenir.
Böyle bir ekranın durumunu uygulayacak olsaydınız ve TypeScript ve @wordpress/data ile bir uygulamanın durumunun nasıl tanımlanacağına dair yazılarımı okuduysanız, bahse girerim şöyle bir şey yapardınız:
type State = { readonly username: string; readonly password: string; readonly isLoggingIn: boolean; readonly errorMessage: string; };devleti yönetmek için gerekli tüm alanlara sahip olan ama… elimizden gelenin en iyisi bu mu?
Sonlu Durum Makineleri
Sonlu durum makinesi (FSM), genel olarak konuşursak, sonlu bir durum kümesini ve bunlar arasındaki olası geçişleri tanımlamamıza izin veren matematiksel bir modeldir. (Wikipedia'da daha fazla bilgi). Bu soyutlamayı seviyorum çünkü iş uygulamalarımızın durumunu modellemeye geldiğinde ihtiyaçlarımıza mükemmel şekilde uyuyor. Örneğin, giriş ekranımızın durum şeması şöyle görünebilir:

gördüğünüz gibi, uygulamak istediğimiz davranışı açık ve özlü bir şekilde yakalar.
Eyaletleri @wordpress/data ile tanımlama
React'e giriş serimizde, uygulamamızın durumunu oluşturmak ve yönetmek için @wordpress/data paketinin nasıl kullanılacağını gördük. Bunu, veri depomuzun ana bileşenlerini tanımlayarak yaptık:
- Durumu sorgulamak için bir dizi seçici
- Bir güncelleme isteğini tetiklemek için bir dizi eylem
- Mevcut durum ve bir güncelleme eylemi verildiğinde, durumu güncellemek için bir indirgeyici işlevi
Özünde, @wordpress/data içindeki depolar zaten sonlu durum makineleri gibi davranır, çünkü redüktör işlevi bir eylem kullanarak bir durumdan diğerine "geçiş yapar":
( state: State, action: Action ) => State @wordpress/data data'da TypeScript ile Sonlu Durum Makinesi Nasıl Tanımlanır
Görünüşe göre @wordpress/data mağazaları, birkaç satır yukarıda tanıttığımız FSM modeline oldukça yakın: sadece içinde olabileceğimiz belirli durumları ve bunlar arasındaki geçişleri kaçırıyoruz… Öyleyse nasıl yapabileceğimize daha yakından bakalım. bu sorunları adım adım düzeltin.
Açık Durumlar
Bu gönderiye, uygulamamızın durumunun olası bir uygulamasını göstererek başladık. İlk teklifimiz, işi yapan bir dizi özellikti, ancak hiç de sonlu durum makinesi gibi görünmüyordu. Öyleyse, durumlarımızın modelimizde açıkça tanımlandığından emin olarak başlayalım:
type State = | Form | LoggingIn | Success | Error; type Form = { readonly status: 'form'; readonly username: string; readonly password: string; }; type LoggingIn = { readonly status: 'logging-in'; readonly username: string; readonly password: string; }; type Success = { readonly status: 'success'; // ... }; type Error = { readonly status: 'error'; readonly message: string; }; Oldukça açık, ha? Durum başına bir tür vardır ve uygulamanın genel durumu, ayrımcı bir tür birliği olarak tanımlanır. (a) durum "farklı türlerin birliği" olduğu için (yani, ya Form ya da LoggingIn ya da her neyse ) ve (b) bir özniteliğimiz var (bu durumda , status ) her an sahip olduğumuz belirli durumu ayırt etmemizi sağlar.
Bence bu çözüm, başlangıçta sahip olduğumuzdan çok daha iyi. Orijinal çözümümüzde "geçersiz durumlar" tanımlayabildik çünkü biri oturum açabiliyor (sadece isLoggingIn değerini true olarak ayarlayın) ve aynı zamanda bir hata durumunda olabilir (hata errorMessage boş olandan başka bir değere ayarlayın) sicim). Ama bu açıkça mantıklı değil! Hangisi o? Giriş mi yapıyoruz yoksa bir hata mı göstermemiz gerekiyor?
Öte yandan yeni çözüm, devleti temsil etme konusunda çok daha kesindir: “geçersiz” durumları “imkansız” kılar. LoggingIn , bir hata mesajı oluşturmanın bir yolu yoktur (bunun için bir öznitelik yoktur!). Error gösteriyorsak giriş yapamazsınız. Çok daha iyi değil mi?
Hareketler
@wordpress/data 'da, eylemler bizim durumumuzda mağazayı günceller. Durumumuzu bir FSM olarak modellediğimiz için, eylemlerimiz orijinal diyagramımızdaki oklar olacaktır. Tek yapmanız gereken, onları ayrımcı bir tür birliği olarak modellemek (sözleşmeye göre, ayrımcı genellikle type olarak adlandırılır) ve gitmeye hazırsınız:
type SetCredentials = { readonly type: 'SET_CREDENTIALS'; readonly username: string; readonly password: string; }; type Login = { readonly type: 'LOGIN'; }; type ShowApp = { readonly type: 'SHOW_APP'; // ... }; type ShowError = { readonly type: 'SHOW_ERROR'; readonly message: string; }; type BackToLoginForm = { readonly type: 'BACK_TO_LOGIN_FORM'; };Eylemlerimizin orijinal diyagramımızdaki okları temsil etmesi gerekiyor, değil mi? Eh, sizi bilmem ama bence bu aksiyon türleri hiç ok gibi görünmüyor! Okların bir yönü vardır (bir durumdan diğerine giderler); eylemler yapmaz.

Kodumuzda, belirli eylemlerin yalnızca bir durumdan diğerine geçmek için yararlı olduğunu açıkça belirtmek için bir yola ihtiyacımız var. Ve şimdiye kadar bulduğum en iyi çözüm, redüktörün kendisini kullanmak:
function reducer( state: State, action: Action ): State { switch ( state.status ) { case 'form': switch ( action.type ) { case 'SET_CREDENTIALS': return { status: 'ready', username: action.username, password: action.password, }; case 'LOGIN': return { ...state, status: 'logging-in' }; } case ...: ... } return state; } Redüktörümüz status süzerek başlarsa state “kaynağı”nı biliriz. Ardından, mevcut eyleme bakarız ve bu bir "dışa doğru ok" ise yeni "hedef" durumunu oluştururuz.
Bunun nasıl çalıştığını yukarıdaki pasajda açıkça görebilirsiniz. Mevcut status form ise, SET_CREDENTIALS eylemi bizi bulunduğumuz duruma götürür (kimlik bilgilerini güncelleme, hah) ve LOGIN eylemi durumu login logging-in değiştirir. Bu durumdaki diğer tüm eylemler yok sayılır ve bu nedenle durum değişmez.
Hepsi iyi ve mükemmel ama… Bilmiyorum, kodu pek sevmiyorum, ya sen? Kodumun açık, özlü, kendi kendini açıklayıcı ve güvenli olduğundan emin olmak istiyorum. Bunu yapabilir miyiz?
Kesinlikle Yazılan Redüktör
Az önce içine girdiğimiz karışıklığı düzeltmek için, sadece reducer yeniden düzenlememiz gerekiyor, böylece FSM'mizdeki her olası durum kendi redüktörüne sahip olacak. Bunu doğru yaparsak, güçlü bir şekilde yazılacaklar ve neye izin verilip neyin verilmediği netleşecek.
Uygulamamızdaki her durum için bir tür oluşturarak başlayalım. Her tür, bizi verilen durumdan başka bir yere götürebilecek eylemleri gruplayacaktır. Örneğin, Form durumumuzda iki adet dışa doğru ok vardır ( SetCredentials ve Login ), bu yüzden iki eylemin birleşimi ile FormAction türünü oluşturalım. Her durum için işlemi tekrarlayın ve şunu elde edin:
type FormAction = SetCredentials | Login; type LoggingInAction = ShowError | ShowApp; type SuccessAction = ...; type ErrorAction = BackToLoginForm; type Action = | FormAction | LoggingInAction | SuccessAction | ErrorAction;Ardından, ihtiyacımız olan tüm redüktörleri tanımlayın. Yine, her eyalet için bir tane:
function reduceForm( state: Form, action: FormAction ): Form | LoggingIn { switch ( action.type ) { switch ( action.type ) { case 'SET_CREDENTIALS': return { status: 'ready', username: action.username, password: action.password, }; case 'LOGIN': return { ...state, status: 'logging-in' }; } } reduceLoggingIn: ( state: LoggingIn, action: LoggingInAction ) => Success | Error reduceError: ( state: Error, action: ErrorAction ) => Form reduceSuccess: ( state: Success, action: SuccessAction ) => ...TypeScript ve az önce tanımladığımız türler sayesinde, artık kaynak durumu ve her bir indirgeyicinin beklediği eylem kümesi ve ayrıca sonuç olarak elde edebileceğimiz hedef durum(lar) hakkında son derece kesin olabiliriz.
Son olarak, hepsini benzersiz bir reducer işleviyle bağlamamız yeterlidir:
function reducer( state: State, action: Action ): State { switch ( state.status ) { case 'form': return reduceForm( state, action as FormActions ) ?? state; case 'logging-in': return reduceLoggingIn( state, action as LoggingInActions ) ?? state; case 'error': return reduceError( state, action as ErrorActions ) ?? state; case 'success': return reduceSuccess( state, action as SuccessActions ) ?? state; } }Ortaya çıkan kod, bence, daha net ve bakımı daha kolay. Üstelik, TypeScript artık ne yaptığımızı doğrulayabilir ve eksik senaryoları yakalayabilir. Mükemmel!
Çözüm
Bu yazıda, bir sonlu durum makinesinin ne olduğunu ve bir @wordpress/data deposunda TypeScript ile nasıl uygulayabileceğimizi gördük. Nihai çözümümüz oldukça basittir ve yine de birçok avantaj sunar: uygulamamızın durumunu ve amacını daha iyi yakalar ve TypeScript'in gücünden yararlanır.
Umarım bu gönderiyi beğenmişsinizdir! Yaptıysanız, lütfen arkadaşlarınızla ve iş arkadaşlarınızla paylaşın. Ve lütfen aşağıdaki yorum bölümünde bu sorunları nasıl çözeceğinizi bana bildirin.
Unsplash'ta Patrick Hendry tarafından öne çıkan görsel.
