Özel hook'larla React'te durum makinelerinin gücünü açığa çıkarın. Karmaşık mantığı soyutlamayı, kodun bakımını iyileştirmeyi öğrenin.
React Özel Hook Durum Makinesi: Karmaşık Durum Mantığı Soyutlamasında Ustalaşmak
React uygulamaları karmaşıklaştıkça, durumu yönetmek önemli bir zorluk haline gelebilir. `useState` ve `useEffect` kullanan geleneksel yaklaşımlar, özellikle karmaşık durum geçişleri ve yan etkilerle uğraşırken hızla karmaşık mantığa ve bakımı zor koda yol açabilir. İşte burada durum makineleri ve özellikle onları uygulayan React özel hook'ları devreye giriyor. Bu makale, durum makineleri kavramını inceleyecek, bunları React'te özel hook'lar olarak nasıl uygulayacağınızı gösterecek ve küresel bir kitle için ölçeklenebilir ve bakımı yapılabilir uygulamalar oluşturma konusunda sundukları faydaları açıklayacaktır.
Durum Makinesi Nedir?
Bir durum makinesi (veya sonlu durum makinesi, FSM), bir sistemin davranışını, sonlu sayıda durumu ve bu durumlar arasındaki geçişleri tanımlayarak açıklayan bir hesaplama matematiksel modelidir. Bunu bir akış şeması gibi düşünün, ancak daha sıkı kurallar ve daha resmi bir tanımla. Temel kavramlar şunları içerir:
- Durumlar: Sistemin farklı koşullarını veya aşamalarını temsil eder.
- Geçişler: Sistemin belirli olaylar veya koşullara bağlı olarak bir durumdan diğerine nasıl geçtiğini tanımlar.
- Olaylar: Durum geçişlerine neden olan tetikleyiciler.
- Başlangıç Durumu: Sistemin başladığı durum.
Durum makineleri, iyi tanımlanmış durumları ve net geçişleri olan sistemleri modellemede mükemmeldir. Gerçek dünya senaryolarında bolca örnek bulunmaktadır:
- Trafik Işıkları: Zamanlayıcılarla tetiklenen geçişlerle Kırmızı, Sarı, Yeşil gibi durumlar arasında döner. Bu, küresel olarak tanınabilir bir örnektir.
- Sipariş İşleme: Bir e-ticaret siparişi "Bekliyor", "İşleniyor", "Gönderildi" ve "Teslim Edildi" durumlarından geçebilir. Bu, çevrimiçi perakende için evrensel olarak geçerlidir.
- Kimlik Doğrulama Akışı: Bir kullanıcı kimlik doğrulama süreci "Oturum Kapalı", "Oturum Açılıyor", "Oturum Açıldı" ve "Hata" gibi durumları içerebilir. Güvenlik protokolleri genellikle ülkeler arasında tutarlıdır.
React'te Neden Durum Makineleri Kullanmalı?
Durum makinelerini React bileşenlerinize entegre etmek birkaç cazip avantaj sunar:
- Geliştirilmiş Kod Organizasyonu: Durum makineleri, durum yönetimine yapılandırılmış bir yaklaşım zorlar, kodunuzu daha öngörülebilir ve anlaşılması daha kolay hale getirir. Artık spagetti kod yok!
- Azaltılmış Karmaşıklık: Durumları ve geçişleri açıkça tanımlayarak karmaşık mantığı basitleştirebilir ve istenmeyen yan etkilerden kaçınabilirsiniz.
- Geliştirilmiş Test Edilebilirlik: Durum makineleri doğası gereği test edilebilir. Her durumu ve geçişi test ederek sisteminizin doğru davrandığını kolayca doğrulayabilirsiniz.
- Artan Bakım Kolaylığı: Durum makinelerinin beyan tabanlı doğası, uygulamanız geliştikçe kodunuzu değiştirmeyi ve genişletmeyi kolaylaştırır.
- Daha İyi Görselleştirmeler: Durum makinelerini görselleştirebilen araçlar mevcuttur ve sisteminizin davranışı hakkında net bir genel bakış sunarak, farklı yeteneklere sahip ekipler arasındaki işbirliği ve anlamayı kolaylaştırır.
Bir Durum Makinesini React Özel Hook Olarak Uygulama
Bir React özel hook'u kullanarak bir durum makinesini nasıl uygulayacağımızı gösterelim. Üç durumda olabilen bir düğme örneği oluşturacağız: `idle`, `loading` ve `success`. Düğme `idle` durumunda başlar. Tıklandığında, `loading` durumuna geçer, bir yükleme sürecini simüle eder (`setTimeout` kullanarak) ve ardından `success` durumuna geçer.
1. Durum Makinesini Tanımlama
İlk olarak, düğme durum makinemizin durumlarını ve geçişlerini tanımlarız:
const buttonStateMachineDefinition = {
initial: 'idle',
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success', // 2 saniye sonra başarı durumuna geçiş
},
},
success: {},
},
};
Bu yapılandırma, durum makinesini tanımlamak için kütüphaneden bağımsız (ancak XState'ten esinlenilmiş) bir yaklaşım kullanır. Bu tanımı yorumlamak için gereken mantığı özel hook'umuzda kendimiz uygulayacağız. `initial` özelliği, başlangıç durumunu `idle` olarak ayarlar. `states` özelliği olası durumları (`idle`, `loading` ve `success`) ve geçişlerini tanımlar. `idle` durumu, bir `CLICK` olayı gerçekleştiğinde `loading` durumuna bir geçiş tanımlayan bir `on` özelliğine sahiptir. `loading` durumu, belirtilen bir süre (2 saniye) sonra otomatik olarak `success` durumuna geçiş yapmak için `after` özelliğini kullanır. `success` durumu, bu örnekte bir sonlandırma durumudur.
2. Özel Hook'u Oluşturma
Şimdi, durum makinesi mantığını uygulayan özel hook'u oluşturalım:
import { useState, useEffect } from 'react';
const useStateMachine = (stateMachineDefinition) => {
const [currentState, setCurrentState] = useState(stateMachineDefinition.initial);
const [context, setContext] = useState({});
const transition = (event) => {
const stateDefinition = stateMachineDefinition.states[currentState];
if (stateDefinition && stateDefinition.on && stateDefinition.on[event]) {
setCurrentState(stateDefinition.on[event]);
}
};
useEffect(() => {
const stateDefinition = stateMachineDefinition.states[currentState];
if (stateDefinition && stateDefinition.after) {
const timeoutKeys = Object.keys(stateDefinition.after);
timeoutKeys.forEach(timeoutKey => {
const timeout = parseInt(timeoutKey, 10);
const nextState = stateDefinition.after[timeoutKey];
const timer = setTimeout(() => {
setCurrentState(nextState);
clearTimeout(timer);
}, timeout);
return () => clearTimeout(timer); // Bileşen kaldırıldığında veya durum değiştiğinde temizleme
});
}
}, [currentState, stateMachineDefinition.states]);
return {
currentState,
context,
transition,
};
};
export default useStateMachine;
Bu `useStateMachine` hook'u, durum makinesi tanımını argüman olarak alır. Mevcut durumu ve bağlamı (bağlamı daha sonra açıklayacağız) yönetmek için `useState` kullanır. `transition` fonksiyonu, bir olayı argüman olarak alır ve durum makinesi tanımında tanımlanan geçişlere göre mevcut durumu günceller. `useEffect` hook'u, belirtilen bir süre sonra otomatik olarak bir sonraki duruma geçmek için zamanlayıcıları ayarlayan `after` özelliğini işler. Hook, mevcut durumu, bağlamı ve `transition` fonksiyonunu döndürür.
3. Özel Hook'u Bir Bileşende Kullanma
Son olarak, özel hook'u bir React bileşeninde kullanalım:
import React from 'react';
import useStateMachine from './useStateMachine';
const buttonStateMachineDefinition = {
initial: 'idle',
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success', // 2 saniye sonra başarı durumuna geçiş
},
},
success: {},
},
};
const MyButton = () => {
const { currentState, transition } = useStateMachine(buttonStateMachineDefinition);
const handleClick = () => {
if (currentState === 'idle') {
transition('CLICK');
}
};
let buttonText = 'Bana Tıkla';
if (currentState === 'loading') {
buttonText = 'Yükleniyor...';
} else if (currentState === 'success') {
buttonText = 'Başarılı!';
}
return (
);
};
export default MyButton;
Bu bileşen, düğmenin durumunu yönetmek için `useStateMachine` hook'unu kullanır. `handleClick` fonksiyonu, düğme tıklandığında (ve yalnızca `idle` durumundaysa) `CLICK` olayını gönderir. Bileşen, mevcut duruma göre farklı metinler görüntüler. Yüklenirken çifte tıklamayı önlemek için düğme devre dışı bırakılır.
Durum Makinelerinde Bağlamı İşleme
Birçok gerçek dünya senaryosunda, durum makinelerinin durum geçişleri boyunca kalıcı olan verileri yönetmesi gerekir. Bu verilere bağlam denir. Bağlam, durum makinesi ilerledikçe ilgili bilgileri depolamanıza ve güncellemenize olanak tanır.
Düğme örneğimizi, düğme her başarılı bir şekilde yüklendiğinde artan bir sayacı içerecek şekilde genişletelim. Durum makinesi tanımını ve bağlamı işlemek için özel hook'u güncelleyeceğiz.
1. Durum Makinesi Tanımını Güncelleme
const buttonStateMachineDefinition = {
initial: 'idle',
context: {
count: 0,
},
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success',
},
},
success: {
entry: (context) => {
return { ...context, count: context.count + 1 };
},
},
},
};
Durum makinesi tanımına başlangıç `count` değeri 0 olan bir `context` özelliği ekledik. Ayrıca `success` durumuna bir `entry` eylemi ekledik. `entry` eylemi, durum makinesi `success` durumuna girdiğinde yürütülür. Mevcut bağlamı argüman olarak alır ve `count` artırılmış yeni bir bağlam döndürür. Buradaki `entry`, bağlamı değiştirmenin bir örneğini gösterir. Javascript nesneleri referansla geçirildiği için, orijinalini değiştirmek yerine *yeni* bir nesne döndürmek önemlidir.
2. Özel Hook'u Güncelleme
import { useState, useEffect } from 'react';
const useStateMachine = (stateMachineDefinition) => {
const [currentState, setCurrentState] = useState(stateMachineDefinition.initial);
const [context, setContext] = useState(stateMachineDefinition.context || {});
const transition = (event) => {
const stateDefinition = stateMachineDefinition.states[currentState];
if (stateDefinition && stateDefinition.on && stateDefinition.on[event]) {
setCurrentState(stateDefinition.on[event]);
}
};
useEffect(() => {
const stateDefinition = stateMachineDefinition.states[currentState];
if(stateDefinition && stateDefinition.entry){
const newContext = stateDefinition.entry(context);
setContext(newContext);
}
if (stateDefinition && stateDefinition.after) {
const timeoutKeys = Object.keys(stateDefinition.after);
timeoutKeys.forEach(timeoutKey => {
const timeout = parseInt(timeoutKey, 10);
const nextState = stateDefinition.after[timeoutKey];
const timer = setTimeout(() => {
setCurrentState(nextState);
clearTimeout(timer);
}, timeout);
return () => clearTimeout(timer); // Bileşen kaldırıldığında veya durum değiştiğinde temizleme
});
}
}, [currentState, stateMachineDefinition.states, context]);
return {
currentState,
context,
transition,
};
};
export default useStateMachine;
Context durumunu `stateMachineDefinition.context` veya bağlam sağlanmazsa boş bir nesne ile başlatmak için `useStateMachine` hook'unu güncelledik. Ayrıca `entry` eylemini işlemek için bir `useEffect` ekledik. Mevcut durumda bir `entry` eylemi olduğunda, onu yürütürüz ve döndürülen değerle bağlamı güncelleriz.
3. Güncellenmiş Hook'u Bir Bileşende Kullanma
import React from 'react';
import useStateMachine from './useStateMachine';
const buttonStateMachineDefinition = {
initial: 'idle',
context: {
count: 0,
},
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success',
},
},
success: {
entry: (context) => {
return { ...context, count: context.count + 1 };
},
},
},
};
const MyButton = () => {
const { currentState, context, transition } = useStateMachine(buttonStateMachineDefinition);
const handleClick = () => {
if (currentState === 'idle') {
transition('CLICK');
}
};
let buttonText = 'Bana Tıkla';
if (currentState === 'loading') {
buttonText = 'Yükleniyor...';
} else if (currentState === 'success') {
buttonText = 'Başarılı!';
}
return (
Sayı: {context.count}
);
};
export default MyButton;
Artık `context.count`'u bileşende erişip görüntülüyoruz. Düğme her başarılı bir şekilde yüklendiğinde, sayaç artacaktır.
Gelişmiş Durum Makinesi Kavramları
Örneğimiz nispeten basit olsa da, durum makineleri çok daha karmaşık senaryoları işleyebilir. İşte dikkate alınması gereken bazı gelişmiş kavramlar:
- Koruyucular (Guards): Bir geçişin gerçekleşmesi için karşılanması gereken koşullar. Örneğin, bir geçiş yalnızca bir kullanıcı kimlik doğrulaması yapıldığında veya belirli bir veri değeri bir eşiği aştığında izin verilebilir.
- Eylemler: Bir duruma girerken veya çıkarken yürütülen yan etkiler. Bunlar API çağrıları yapmak, DOM'u güncellemek veya diğer bileşenlere olaylar göndermek olabilir.
- Paralel Durumlar: Birden fazla eşzamanlı etkinliği modellemenize olanak tanır. Örneğin, bir video oynatıcı oynatma kontrolleri (oynat, duraklat, durdur) için bir durum makinesi ve video kalitesini yönetmek için başka bir tane (düşük, orta, yüksek) olabilir.
- Hiyerarşik Durumlar: Durumların içine durumlar yerleştirmenize olanak tanır ve durumların bir hiyerarşisini oluşturur. Bu, birçok ilgili duruma sahip karmaşık sistemleri modellemek için faydalı olabilir.
Alternatif Kütüphaneler: XState ve Daha Fazlası
Özel hook'umuz durum makinesinin temel bir uygulamasını sağlarken, birkaç mükemmel kütüphane süreci basitleştirebilir ve daha gelişmiş özellikler sunabilir.
XState
XState, durum makineleri ve durum çizelgeleri oluşturmak, yorumlamak ve yürütmek için popüler bir JavaScript kütüphanesidir. Koruyucular, eylemler, paralel durumlar ve hiyerarşik durumlar dahil olmak üzere karmaşık durum makinelerini tanımlamak için güçlü ve esnek bir API sağlar. XState ayrıca durum makinelerini görselleştirmek ve hata ayıklamak için mükemmel araçlar sunar.
Diğer Kütüphaneler
Diğer seçenekler şunları içerir:
- Robot: Basitlik ve performansa odaklanan hafif bir durum yönetimi kütüphanesi.
- react-automata: Durum makinelerini React bileşenlerine entegre etmek için özel olarak tasarlanmış bir kütüphane.
Kütüphane seçimi projenizin özel ihtiyaçlarına bağlıdır. XState karmaşık durum makineleri için iyi bir seçimdir, Robot ve react-automata ise daha basit senaryolar için uygundur.
Durum Makineleri Kullanımı İçin En İyi Uygulamalar
React uygulamalarınızda durum makinelerinden etkili bir şekilde yararlanmak için aşağıdaki en iyi uygulamaları göz önünde bulundurun:
- Küçük Başlayın: Basit durum makineleriyle başlayın ve gerektiğinde karmaşıklığı kademeli olarak artırın.
- Durum Makinenizi Görselleştirin: Durum makinenizin davranışı hakkında net bir anlayışa sahip olmak için görselleştirme araçları kullanın.
- Kapsamlı Testler Yazın: Sisteminizin doğru davrandığından emin olmak için her durumu ve geçişi kapsamlı bir şekilde test edin.
- Durum Makinenizi Belgeleyin: Durum makinenizin durumlarını, geçişlerini, koruyucularını ve eylemlerini açıkça belgeleyin.
- Uluslararasılaştırmayı (i18n) Göz Önünde Bulundurun: Uygulamanız küresel bir kitleye hitap ediyorsa, durum makinesi mantığınızın ve kullanıcı arayüzünüzün düzgün bir şekilde uluslararasılaştırıldığından emin olun. Örneğin, kullanıcının yerel ayarlarına göre farklı tarih biçimlerini veya para birimi sembollerini işlemek için ayrı durum makineleri veya bağlam kullanın.
- Erişilebilirlik (a11y): Durum geçişlerinizin ve UI güncellemelerinizin engelli kullanıcılar için erişilebilir olduğundan emin olun. Doğru bağlamı ve geri bildirimi yardımcı teknolojilere sağlamak için ARIA özniteliklerini ve anlamsal HTML'i kullanın.
Sonuç
React özel hook'ları, durum makineleriyle birleştiğinde, React uygulamalarında karmaşık durum mantığını yönetmek için güçlü ve etkili bir yaklaşım sağlar. Durum geçişlerini ve yan etkilerini iyi tanımlanmış bir modele soyutlayarak, kod organizasyonunu iyileştirebilir, karmaşıklığı azaltabilir, test edilebilirliği artırabilir ve bakım kolaylığını artırabilirsiniz. Özel hook'unuzu kendiniz uygulayın veya XState gibi bir kütüphaneden yararlanın, durum makinelerini React iş akışınıza dahil etmek, dünya çapındaki kullanıcılar için uygulamalarınızın kalitesini ve ölçeklenebilirliğini önemli ölçüde artırabilir.