Redux State Management ile Üst Düzey Kontrol: Bir ClojureScript Eğitimi
Yayınlanan: 2022-03-11Unearthing ClojureScript'in ikinci heyecan verici bölümü için tekrar hoş geldiniz! Bu yazıda, ClojureScript ile ciddileşmek için bir sonraki büyük adımı ele alacağım: durum yönetimi - bu durumda React kullanarak.
Ön uç yazılım ile durum yönetimi çok önemlidir. Kullanıma hazır, React'te durumu ele almanın birkaç yolu vardır:
- Durumu en üst düzeyde tutmak ve onu (veya belirli bir durum parçası için işleyicileri) alt bileşenlere geçirmek.
- Pencereden saflığı atmak ve küresel değişkenlere veya bir tür Lovecraftian bağımlılık enjeksiyonuna sahip olmak.
Genel olarak konuşursak, bunların hiçbiri harika değil. Durumu en üst düzeyde tutmak oldukça basittir, ancak uygulama durumunu ona ihtiyaç duyan her bileşene aktarmanın büyük miktarda ek yükü vardır.
Karşılaştırıldığında, genel değişkenlere (veya durumun diğer saf sürümlerine) sahip olmak, izlenmesi zor eşzamanlılık sorunlarına neden olabilir ve bu da bileşenlerin beklediğiniz zaman güncellenmemesine neden olabilir veya bunun tersi de geçerlidir.
Peki bunun üstesinden nasıl gelinebilir? React'e aşina olanlarınız için, JavaScript uygulamaları için bir durum konteyneri olan Redux'u denemiş olabilirsiniz. Durumu korumak için cesurca yönetilebilir bir sistem arayarak bunu kendi iradenizle bulmuş olabilirsiniz. Veya JavaScript ve diğer web araçları hakkında okurken tesadüfen rastlamış olabilirsiniz.
İnsanların Redux'a nasıl baktıklarına bakılmaksızın, deneyimlerime göre genellikle iki düşünceyle sonuçlanırlar:
- "Bunu kullanmam gerektiğini hissediyorum çünkü herkes kullanmam gerektiğini söylüyor."
- “Bunun neden daha iyi olduğunu gerçekten tam olarak anlamıyorum.”
Genel olarak konuşursak, Redux durum yönetiminin React'in reaktif doğasına uymasını sağlayan bir soyutlama sağlar. Tüm durum bilgisini Redux gibi bir sisteme yükleyerek React'in saflığını korursunuz. Böylece çok daha az baş ağrınız olacak ve genellikle akıl yürütmesi çok daha kolay bir şeyle karşılaşacaksınız.
Clojure'a Yeni Başlayanlar İçin
Bu, ClojureScript'i tamamen sıfırdan öğrenmenize yardımcı olmasa da, burada en azından Clojure[Script]'teki bazı temel durum kavramlarını özetleyeceğim. Zaten deneyimli bir Clojurian iseniz bu bölümleri atlamaktan çekinmeyin!
ClojureScript için de geçerli olan Clojure temellerinden birini hatırlayın: Varsayılan olarak, veriler değişmezdir. Bu, geliştirmek ve N zaman adımında yarattığınızın > N zaman adımında hala aynı olduğunu garanti etmek için harikadır. ClojureScript ayrıca, atom
kavramı aracılığıyla, ihtiyacımız olduğunda değişebilir duruma sahip olmak için bize uygun bir yol sağlar.
ClojureScript'teki bir atom
, Java'daki bir AtomicReference
çok benzer: İçeriğini eşzamanlılık garantileriyle kilitleyen yeni bir nesne sağlar. Tıpkı Java'da olduğu gibi, bu nesneye istediğiniz her şeyi yerleştirebilirsiniz - o andan itibaren o atom istediğiniz şeye atomik bir referans olacaktır.
Atomunuzu aldıktan sonra, reset!
kullanarak atom
yeni bir değer ayarlayabilirsiniz! işlev (işlevdeki !
'ye dikkat edin - Clojure dilinde bu, genellikle bir işlemin durum bilgisi olduğunu veya saf olmadığını belirtmek için kullanılır).
Ayrıca, Java'dan farklı olarak Clojure'un atom
ne koyduğunuzla ilgilenmediğini unutmayın. Bir dize, bir liste veya bir nesne olabilir. Dinamik yazma, bebeğim!
(def my-mutable-map (atom {})) ; recall that {} means an empty map in Clojure (println @my-mutable-map) ; You 'dereference' an atom using @ ; -> this prints {} (reset! my-mutable-map {:hello "there"}) ; atomically set the atom (reset! my-mutable-map "hello, there!") ; don't forget Clojure is dynamic :)
Reaktif, bu atom kavramını kendi atom
genişletir. (Reagent'a aşina değilseniz, bundan önceki gönderiye bakın.) Bu, ClojureScript atom
ile aynı şekilde davranır, ancak React'in yerleşik durum deposu gibi Reagent'ta render olaylarını da tetikler.
Bir örnek:
(ns example (:require [reagent.core :refer [atom]])) ; in this module, atom now refers ; to reagent's atom. (def my-atom (atom "world!")) (defn component [] [:div [:span "Hello, " @my-atom] [:input {:type "button" :value "Press Me!" :on-click #(reset! My-atom "there!")}]])
Bu, "Merhaba dünya!" Diyen bir <span>
içeren tek bir <div>
gösterecektir. ve beklediğiniz gibi bir düğme. Bu düğmeye basmak, atomumu "there!"
içerecek şekilde my-atom
olarak mutasyona uğratacaktır. . Bu, bileşenin yeniden çizilmesini tetikleyecek ve yayılmanın "Merhaba, orada!" demesine neden olacaktır. Bunun yerine.
Bu, yerel, bileşen düzeyinde mutasyon için yeterince basit görünüyor, ancak ya birden fazla soyutlama düzeyine sahip daha karmaşık bir uygulamamız varsa? Veya birden çok alt bileşen ve bunların alt bileşenleri arasında ortak bir durumu paylaşmamız gerekirse?
Daha Karmaşık Bir Örnek
Bunu bir örnekle inceleyelim. Burada kaba bir giriş sayfası uygulayacağız:
(ns unearthing-clojurescript.login (:require [reagent.core :as reagent :refer [atom]])) ;; -- STATE -- (def username (atom nil)) (def password (atom nil)) ;; -- VIEW -- (defn component [on-login] [:div [:b "Username"] [:input {:type "text" :value @username :on-change #(reset! username (-> % .-target .-value))}] [:b "Password"] [:input {:type "password" :value @password :on-change #(reset! password (-> % .-target .-value))}] [:input {:type "button" :value "Login!" :on-click #(on-login @username @password)}]])
Daha sonra bu oturum açma bileşenini ana app.cljs
içinde şu şekilde barındıracağız:
(ns unearthing-clojurescript.app (:require [unearthing-clojurescript.login :as login])) ;; -- STATE (def token (atom nil)) ;; -- LOGIC -- (defn- do-login-io [username password] (let [t (complicated-io-login-operation username password)] (reset! token t))) ;; -- VIEW -- (defn component [] [:div [login/component do-login-io]])
Beklenen iş akışı şu şekildedir:
- Kullanıcının kullanıcı adını ve şifresini girmesini ve gönder tuşuna basmasını bekliyoruz.
- Bu, ana bileşendeki
do-login-io
işlevimizi tetikleyecektir. -
do-login-io
işlevi bazı G/Ç işlemlerini gerçekleştirir (bir sunucuda oturum açma ve bir belirteç alma gibi).
Bu işlem engelliyorsa, uygulamamız donmuş olduğu için zaten bir yığın beladayız - değilse, o zaman endişelenmemiz gereken zaman uyumsuzluğu var!
Ek olarak, şimdi bu jetonu sunucumuza sorgu yapmak isteyen tüm alt bileşenlerimize sağlamamız gerekiyor. Kodu yeniden düzenleme artık çok daha zor!
Son olarak, bileşenimiz artık tamamen reaktif değil - artık uygulamanın geri kalanının durumunu yönetmede, G/Ç'yi tetiklemede ve genellikle biraz sıkıntı yaratmada suç ortağıdır.
ClojureScript Eğitimi: Redux Girin
Redux, devlet temelli tüm hayallerinizi gerçeğe dönüştüren sihirli değnektir. Düzgün bir şekilde uygulandığında, güvenli, hızlı ve kullanımı kolay bir durum paylaşımlı soyutlama sağlar.
Redux'un iç işleyişi (ve arkasındaki teori) bu makalenin kapsamı dışındadır. Bunun yerine, neler yapabileceğini göstermenin bir yolunu bulmasını umduğum ClojureScript ile çalışan bir örneğe dalacağım!
Bizim bağlamımızda Redux, mevcut birçok ClojureScript kitaplığından biri tarafından uygulanmaktadır; buna yeniden çerçeve denir. Redux çevresinde (bence) kullanımı mutlak bir zevk haline getiren Clojure ile birleştirilmiş bir sarmalayıcı sağlar.
Temeller
Redux, uygulama durumunuzu kaldırarak bileşenlerinizi hafif tutar. Reduxified bileşeninin yalnızca şunları düşünmesi gerekir:
- Ne gibi görünüyor
- Hangi verileri tüketir
- Hangi olayları tetikler
Gerisi perde arkasında halledilir.
Bu noktayı vurgulamak için, yukarıdaki giriş sayfamızı Reduxify edelim.

Veritabanı
Her şeyden önce: Uygulama modelimizin nasıl görüneceğine karar vermemiz gerekiyor. Bunu, uygulama boyunca erişilebilecek verilerimizin şeklini tanımlayarak yapıyoruz.
İyi bir kural, verilerin birden fazla Redux bileşeninde kullanılması gerekiyorsa veya uzun ömürlü olması gerekiyorsa (tokenimizin olacağı gibi), o zaman veritabanında saklanması gerektiğidir. Buna karşılık, veriler bileşen için yerel ise (kullanıcı adı ve parola alanlarımız gibi), yerel bileşen durumu olarak yaşamalı ve veritabanında saklanmamalıdır.
Veritabanı ortak plakamızı oluşturalım ve jetonumuzu belirleyelim:
(ns unearthing-clojurescript.state.db (:require [cljs.spec.alpha :as s] [re-frame.core :as re-frame])) (s/def ::token string?) (s/def ::db (s/keys :opt-un [::token])) (def default-db {:token nil})
Burada dikkat edilmesi gereken birkaç ilginç nokta var:
- Verilerimizin nasıl görünmesi gerektiğini açıklamak için
spec
özellik kitaplığını kullanıyoruz. Bu özellikle Clojure[Script] gibi dinamik bir dilde uygundur. - Bu örnek için, yalnızca kullanıcımız oturum açtıktan sonra temsil edecek olan global bir jetonun kaydını tutuyoruz. Bu jeton basit bir dizedir.
- Ancak, kullanıcı oturum açmadan önce bir jetonumuz olmayacak. Bu, "isteğe bağlı, niteliksiz" anlamına gelen
:opt-un
anahtar sözcüğüyle temsil edilir. (Clojure'da, normal bir anahtar kelime:cat
gibi bir şey olabilirken, nitelikli bir anahtar kelime:animal/cat
gibi bir şey olabilir. Niteleme normalde modül düzeyinde gerçekleşir—bu, farklı modüllerdeki anahtar kelimelerin birbirini kesmesini engeller.) - Son olarak, veritabanımızın varsayılan durumunu bu şekilde başlatıyoruz.
Herhangi bir zamanda, veritabanımızdaki verilerin buradaki spesifikasyonumuzla eşleştiğinden emin olmalıyız.
abonelikler
Artık veri modelimizi tanımladığımıza göre, görüşümüzün bu verileri nasıl gösterdiğini yansıtmamız gerekiyor. Redux bileşenimizde görünümümüzün nasıl göründüğünü zaten tanımladık - şimdi yalnızca görünümümüzü veritabanımıza bağlamamız gerekiyor.
Redux ile veritabanımıza doğrudan erişmiyoruz; bu, yaşam döngüsü ve eşzamanlılık sorunlarına neden olabilir. Bunun yerine, abonelikler aracılığıyla veritabanının bir yönü ile olan ilişkimizi kaydederiz.
Bir abonelik, yeniden çerçeveleme (ve Reaktife) veritabanının bir bölümüne bağımlı olduğumuzu ve bu bölüm değiştirilirse Redux bileşenimizin yeniden oluşturulması gerektiğini söyler.
Abonelikleri tanımlamak çok basittir:
(ns unearthing-clojurescript.state.subs (:require [re-frame.core :refer [reg-sub]])) (reg-sub :token ; <- the name of the subscription (fn [{:keys [token] :as db} _] ; first argument is the database, second argument is any token)) ; args passed to the subscribe function (not used here)
Burada, jetonun kendisine tek bir abonelik kaydederiz. Abonelik, yalnızca aboneliğin adı ve bu öğeyi veritabanından çıkaran işlevdir. Bu değere istediğimizi yapabilir ve burada istediğimiz kadar görüntüyü değiştirebiliriz; bununla birlikte, bu durumda, biz sadece belirteci veritabanından çıkarıp iade ediyoruz.
Aboneliklerle yapabileceğiniz çok, çok daha fazlası var (örneğin, yeniden oluşturma konusunda daha kapsamlı bir kapsam için veritabanının alt bölümlerine ilişkin görünümler tanımlamak gibi), ancak şimdilik bunu basit tutacağız!
Olaylar
Veritabanımız var ve veritabanına ilişkin görüşümüz var. Şimdi bazı olayları tetiklememiz gerekiyor! Bu örnekte iki tür olayımız var:
- Veritabanına yeni bir jeton yazmanın saf olayı (yan etkisi olmayan ).
- Dışarı çıkıp bazı müşteri etkileşimi yoluyla jetonumuzu istemenin G/Ç olayı (yan etkisi olan ).
Kolay olanla başlayacağız. Yeniden çerçeveleme, tam olarak bu tür bir olay için bir işlev bile sağlar:
(ns unearthing-clojurescript.state.events (:require [re-frame.core :refer [reg-event-db reg-event-fx reg-fx] :as rf] [unearthing-clojurescript.state.db :refer [default-db]])) ; our start up event that initialises the database. ; we'll trigger this in our core.cljs (reg-event-db :initialise-db (fn [_ _] default-db)) ; a simple event that places a token in the database (reg-event-db :store-login (fn [db [_ token]] (assoc db :token token)))
Yine, burada oldukça basittir - iki olay tanımladık. Birincisi, veritabanımızı başlatmak içindir. (Her iki argümanını nasıl yok saydığını görüyor musunuz? Veritabanını her zaman default-db
!) İkincisi, bir kez aldığımızda jetonumuzu depolamak içindir.
Bu olayların hiçbirinin yan etkisi olmadığına dikkat edin - harici arama yok, G/Ç yok! Bu, kutsal Redux sürecinin kutsallığını korumak için çok önemlidir. Redux'un gazabını üzerinize çekmemek için onu kirli kılmayın.
Son olarak, login etkinliğimize ihtiyacımız var. Diğerlerinin altına yerleştireceğiz:
(reg-event-fx :login (fn [{:keys [db]} [_ credentials]] {:request-token credentials})) (reg-fx :request-token (fn [{:keys [username password]}] (let [token (complicated-io-login-operation username password)] (rf/dispatch [:store-login token]))))
reg-event-fx
işlevi, bazı küçük farklılıklar olmasına rağmen, büyük ölçüde reg-event-db
işlevine benzer.
- İlk argüman artık sadece veritabanının kendisi değil. Uygulama durumunu yönetmek için kullanabileceğiniz çok sayıda başka şey içerir.
- İkinci argüman,
reg-event-db
gibidir. - Sadece yeni
db
döndürmek yerine, bunun yerine bu olay için gerçekleşmesi gereken tüm efektleri ("fx") temsil eden bir harita döndürürüz. Bu durumda, aşağıda tanımlanan:request-token
efektini basitçe çağırırız. Diğer geçerli etkilerden biri, basitçe başka bir olayı çağıran:dispatch
.
Efektimiz gönderildikten sonra, uzun süredir devam eden G/Ç oturum açma işlemimizi gerçekleştiren :request-token
çağrılır. Bu bittiğinde, sonucu mutlu bir şekilde olay döngüsüne geri gönderir, böylece döngü tamamlanır!
ClojureScript Eğitimi: Nihai Sonuç
Böyle! Depolama soyutlamamızı tanımladık. Bileşen şimdi nasıl görünüyor?
(ns unearthing-clojurescript.login (:require [reagent.core :as reagent :refer [atom]] [re-frame.core :as rf])) ;; -- STATE -- (def username (atom nil)) (def password (atom nil)) ;; -- VIEW -- (defn component [] [:div [:b "Username"] [:input {:type "text" :value @username :on-change #(reset! username (-> % .-target .-value))}] [:b "Password"] [:input {:type "password" :value @password :on-change #(reset! password (-> % .-target .-value))}] [:input {:type "button" :value "Login!" :on-click #(rf/dispatch [:login {:username @username :password @password]})}]])
Ve uygulama bileşenimiz:
(ns unearthing-clojurescript.app (:require [unearthing-clojurescript.login :as login])) ;; -- VIEW -- (defn component [] [:div [login/component]])
Ve son olarak, bazı uzak bileşenlerde belirtecimize erişmek şu kadar basittir:
(let [token @(rf/subscribe [:token])] ; ... )
Hepsini bir araya koy:
Yaygara yok, kargaşa yok.
Bileşenleri Redux/Re-frame ile Ayırmak, Temiz Durum Yönetimi Demektir
Redux kullanarak (yeniden çerçeveleme yoluyla), görünüm bileşenlerimizi durum işleme karmaşasından başarıyla ayırdık. Durum soyutlamamızı genişletmek artık çocuk oyuncağı!
Redux in ClojureScript gerçekten bu kadar kolay; denememek için hiçbir bahaneniz yok.
Çatlamaya hazırsanız, fantastik yeniden çerçeveleme belgelerine ve basit işlenmiş örneğimize göz atmanızı tavsiye ederim. Aşağıdaki ClojureScript öğreticisi hakkındaki yorumlarınızı okumak için sabırsızlanıyorum. İyi şanslar!