Flux ve Backbone Kullanan React Uygulamalarında Basit Veri Akışı: Örneklerle Bir Eğitim

Yayınlanan: 2022-03-11

React.js harika bir kütüphanedir. Bazen Python'u dilimlediğinden beri en iyi şey gibi görünüyor. Ancak React, bir ön uç uygulama yığınının yalnızca bir parçasıdır. Veri ve durum yönetimi söz konusu olduğunda sunacak çok şeyi yoktur.

React'in yapımcıları olan Facebook, Flux şeklinde bazı rehberlik teklifinde bulundu. Flux, React Views, Action Dispatcher ve Stores kullanılarak tek yönlü veri akışı etrafında oluşturulmuş bir “Uygulama Mimarisi”dir (bir çerçeve değil). Flux modeli, React uygulamalarını akıl yürütmeyi, geliştirmeyi ve sürdürmeyi çok daha kolay hale getiren olay kontrolünün önemli ilkelerini somutlaştırarak bazı büyük sorunları çözer.

Burada, kontrol akışının temel Flux örneklerini tanıtacağım, Mağazalar için neyin eksik olduğunu ve boşluğu “Akı uyumlu” bir şekilde doldurmak için Omurga Modelleri ve Koleksiyonlarının nasıl kullanılacağını tartışacağım.

(Not: Kolaylık ve kısalık sağlamak için örneklerimde CoffeeScript kullanıyorum. CoffeeScript olmayan geliştiriciler de takip edebilmeli ve örnekleri sözde kod olarak değerlendirebilmelidir.)

Facebook Flux'a Giriş

Omurga, Görünümler, Modeller, Koleksiyonlar ve Rotalar içeren mükemmel ve iyi incelenmiş küçük bir kitaplıktır. Bu, yapılandırılmış ön uç uygulamaları için fiili bir standart kitaplıktır ve 2013'te piyasaya sürülmesinden bu yana React uygulamaları ile eşleştirilmiştir. Şimdiye kadar Facebook.com dışındaki React örneklerinin çoğu, Backbone'un tandem olarak kullanıldığını içeriyor.

Ne yazık ki, tüm uygulama akışını React's Views dışında halletmek için tek başına Backbone'a yaslanmak talihsiz komplikasyonlara yol açar. React-Omurga uygulama kodu üzerinde ilk çalışmaya başladığımda, hakkında okuduğum “karmaşık olay zincirlerinin” hidra benzeri kafalarını desteklemesi uzun sürmedi. Kullanıcı Arabiriminden Modellere ve ardından bir Modelden diğerine ve sonra tekrar olay göndermek, kimin kimi, hangi sırayla ve neden değiştirdiğini takip etmeyi zorlaştırır.

Bu Flux öğreticisi, Flux modelinin bu sorunları etkileyici bir kolaylık ve basitlikle nasıl ele aldığını gösterecektir.

Genel Bakış

Flux'un sloganı “tek yönlü veri akışı”dır. İşte bu akışın neye benzediğini gösteren Flux belgelerindeki kullanışlı bir diyagram:

Facebook Flux, React ve Backbone ile eşleştirildiğinde biraz değişen bir "tek yönlü veri akışı" modeli kullanır.

Önemli olan, React --> Dispatcher --> Stores --> React akmasıdır.

Ana bileşenlerin her birinin ne olduğuna ve nasıl bağlandıklarına bakalım:

Dokümanlar ayrıca bu önemli uyarıyı sunar:

Flux, bir çerçeveden çok bir kalıptır ve herhangi bir katı bağımlılığı yoktur. Ancak, Mağazalar için genellikle EventEmitter'ı ve Görüşlerimiz için React'i kullanırız. Başka yerde kolayca bulunamayan tek Flux parçası Dispatcher'dır. Bu modül, Flux araç kutunuzu tamamlamak için burada mevcuttur.

Yani Flux'un üç bileşeni vardır:

  1. Görünümler ( React = require('react') )
  2. Sevkiyatçı ( Dispatcher = require('flux').Dispatcher )
  3. Mağazalar ( EventEmitter = require('events').EventEmitter )
    • (veya yakında göreceğimiz gibi, Backbone = require('backbone') )

Görünümler

React'i burada tarif etmeyeceğim, çünkü onun hakkında çok şey yazıldı, onu Angular'a büyük ölçüde tercih ettiğimi söylemek dışında. Angular'ın aksine, React kodunu yazarken neredeyse hiç kafam karışmıyor, ancak elbette görüşler değişecektir.

Gönderici

Flux Dispatcher, Mağazalarınızı değiştiren tüm olayların işlendiği tek bir yerdir. Bunu kullanmak için, her bir Mağazanın tüm olayları işlemek üzere tek bir geri arama register sağlayın. Ardından, bir Mağazayı ne zaman değiştirmek isterseniz, bir olay dispatch .

React gibi, Dispatcher de bana iyi uygulanmış iyi bir fikir gibi geldi. Örnek olarak, kullanıcının yapılacaklar listesine öğe eklemesine izin veren bir uygulama aşağıdakileri içerebilir:

 # in TodoDispatcher.coffee Dispatcher = require("flux").Dispatcher TodoDispatcher = new Dispatcher() # That's all it takes!. module.exports = TodoDispatcher
 # in TodoStore.coffee TodoDispatcher = require("./TodoDispatcher") TodoStore = {items: []} TodoStore.dispatchCallback = (payload) -> switch payload.actionType when "add-item" TodoStore.items.push payload.item when "delete-last-item" TodoStore.items.pop() TodoStore.dispatchToken = TodoDispatcher.registerCallback(TodoStore.dispatchCallback) module.exports = TodoStore
 # in ItemAddComponent.coffee TodoDispatcher = require("./TodoDispatcher") ItemAddComponent = React.createClass handleAddItem: -> # note: you're NOT just pushing directly to the store! # (the restriction of moving through the dispatcher # makes everything much more modular and maintainable) TodoDispatcher.dispatch actionType: "add-item" item: "hello world" render: -> React.DOM.button { onClick: @handleAddItem }, "Add an Item!"

Bu, iki soruyu yanıtlamayı gerçekten kolaylaştırır:

  1. S: MyStore değiştiren tüm olaylar nelerdir?
    • C: MyStore.dispatchCallback içindeki switch ifadesindeki durumları kontrol edin.
  2. S: Bu olayın olası tüm kaynakları nelerdir?
    • A: Basitçe bu actionType .

Bu, örneğin MyModel.set ve MyModel.save ve MyCollection.add vb. aramaktan çok daha kolaydır, burada bu temel soruların yanıtlarını takip etmek gerçekten çok hızlı bir şekilde zorlaşır.

Dispatcher ayrıca waitFor kullanarak geri aramaların basit, senkronize bir şekilde sıralı olarak çalıştırılmasını sağlar. Örneğin:

 # in MessageStore.coffee MyDispatcher = require("./MyDispatcher") TodoStore = require("./TodoStore") MessageStore = {items: []} MessageStore.dispatchCallback = (payload) -> switch payload.actionType when "add-item" # synchronous event flow! MyDispatcher.waitFor [TodoStore.dispatchToken] MessageStore.items.push "You added an item! It was: " + payload.item module.exports = MessageStore

Pratikte, Mağazalarımı değiştirmek için Dispatcher kullanırken, waitFor kullanmadan bile kodumun ne kadar temiz olduğunu görünce şok oldum.

Dükkanlar

Böylece veriler, Gönderici aracılığıyla Mağazalara akar . Anladım. Ancak, Mağazalardan Görünümlere (yani React) veri akışı nasıl olur? Flux belgelerinde belirtildiği gibi:

[The] görünümü, bağlı olduğu mağazalar tarafından yayınlanan olayları dinler.

Tamam harika. Mağazalarımıza geri arama kaydettiğimiz gibi, Görüşlerimizle (React Bileşenleri olan) geri aramaları kaydederiz. Teçhizatlarından geçirilen Store'da bir değişiklik olduğunda props yeniden render söylüyoruz.

Örneğin:

 # in TodoListComponent.coffee React = require("react") TodoListComponent = React.createClass componentDidMount: -> @props.TodoStore.addEventListener "change", => @forceUpdate() , @ componentWillUnmount: -> # remove the callback render: -> # show the items in a list. React.DOM.ul {}, @props.TodoStore.items.map (item) -> React.DOM.li {}, item

Mükemmel!

Peki bu "change" olayını nasıl yayarız? Flux, EventEmitter kullanılmasını önerir. Resmi bir örnekten:

 var MessageStore = merge(EventEmitter.prototype, { emitChange: function() { this.emit(CHANGE_EVENT); }, /** * @param {function} callback */ addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, get: function(id) { return _messages[id]; }, getAll: function() { return _messages; }, // etc...

Brüt! Ne zaman basit bir Mağaza istesem, hepsini kendim yazmak zorunda mıyım? Görüntülemek istediğim bir bilgi parçasına sahip olduğum her zaman hangisini kullanmam gerekiyor? Daha iyi bir yol olmalı!

Eksik Parça

Backbone'un Modelleri ve Koleksiyonları, Flux'un EventEmitter tabanlı Mağazalarının yaptığı her şeye zaten sahiptir.

Flux, size ham EventEmitter kullanmanızı söyleyerek, her Mağaza oluşturduğunuzda Backbone Modellerinin ve Koleksiyonlarının belki %50-75'ini yeniden oluşturmanızı tavsiye ediyor. Mağazalarınız için EventEmitter'ı kullanmak, Express.js veya eşdeğeri gibi iyi oluşturulmuş mikro çerçeveler zaten mevcutken, tüm temel bilgileri ve ortak özellikleri halletmek için sunucunuz için çıplak Node.js kullanmaya benzer.

Express.js'nin Node.js üzerine inşa edilmesi gibi, Backbone'un Modelleri ve Koleksiyonları da EventEmitter üzerine inşa edilmiştir. Ve hemen hemen her zaman ihtiyacınız olan her şeye sahiptir: Omurga, change olayları yayar ve sorgulama yöntemlerine, alıcılara ve ayarlayıcılara ve her şeye sahiptir. Artı, Backbone'dan Jeremy Ashkenas ve 230 katılımcıdan oluşan ordusu, tüm bu konularda benim yapabileceğimden çok daha iyi bir iş çıkardı.

Bu Omurga öğreticisi için bir örnek olarak, MessageStore örneğini yukarıdan bir Omurga sürümüne dönüştürdüm.

Nesnel olarak daha az koddur (çalışmayı kopyalamaya gerek yoktur) ve öznel olarak daha açık ve özlüdür (örneğin, _messages[message.id] = message yerine this.add(message) ).

O halde Backbone'u Mağazalar için kullanalım!

FluxBone Modeli: Backbone Tarafından Flux Depoları

Bu eğitim, Mağazalar için Backbone kullanan Flux mimarisi olan FluxBone adını gururla verdiğim bir yaklaşımın temelidir. İşte bir FluxBone mimarisinin temel modeli:

  1. Mağazalar, Göndericiye bir geri arama kaydettiren, somutlaştırılmış Omurga Modelleri veya Koleksiyonlardır. Tipik olarak, bu onların tekil oldukları anlamına gelir.
  2. Görünüm bileşenleri hiçbir zaman Mağazaları doğrudan değiştirmez (örneğin, .set() yok). Bunun yerine bileşenler, Eylemleri Göndericiye gönderir.
  3. Bileşenleri görüntüleyin, Mağazalar sorgulayın ve güncellemeleri tetiklemek için olaylarına bağlanın.

Bu Backbone öğreticisi, React uygulamalarında Backbone ve Flux'un birlikte çalışma şeklini incelemek için tasarlanmıştır.

Sırasıyla bunun her bir parçasına bakmak için Omurga ve Akı örneklerini kullanalım:

1. Mağazalar, Göndericiye bir geri arama kaydettiren, somutlaştırılmış Omurga Modelleri veya Koleksiyonlardır.

 # in TodoDispatcher.coffee Dispatcher = require("flux").Dispatcher TodoDispatcher = new Dispatcher() # That's all it takes! module.exports = TodoDispatcher
 # in stores/TodoStore.coffee Backbone = require("backbone") TodoDispatcher = require("../dispatcher") TodoItem = Backbone.Model.extend({}) TodoCollection = Backbone.Collection.extend model: TodoItem url: "/todo" # we register a callback with the Dispatcher on init. initialize: -> @dispatchToken = TodoDispatcher.register(@dispatchCallback) dispatchCallback: (payload) => switch payload.actionType # remove the Model instance from the Store. when "todo-delete" @remove payload.todo when "todo-add" @add payload.todo when "todo-update" # do stuff... @add payload.todo, merge: true # ... etc # the Store is an instantiated Collection; a singleton. TodoStore = new TodoCollection() module.exports = TodoStore

2. Bileşenler hiçbir zaman Mağazaları doğrudan değiştirmez (örneğin, .set() yok). Bunun yerine bileşenler, Eylemleri Göndericiye gönderir.

 # components/TodoComponent.coffee React = require("react") TodoListComponent = React.createClass handleTodoDelete: -> # instead of removing the todo from the TodoStore directly, # we use the Dispatcher TodoDispatcher.dispatch actionType: "todo-delete" todo: @props.todoItem # ... (see below) ... module.exports = TodoListComponent

3. Bileşenler, güncellemeleri tetiklemek için Mağazaları sorgular ve olaylarına bağlanır.

 # components/TodoComponent.coffee React = require("react") TodoListComponent = React.createClass handleTodoDelete: -> # instead of removing the todo from the TodoStore directly, # we use the dispatcher. #flux TodoDispatcher.dispatch actionType: "todo-delete" todo: @props.todoItem # ... componentDidMount: -> # the Component binds to the Store's events @props.TodoStore.on "add remove reset", => @forceUpdate() , @ componentWillUnmount: -> # turn off all events and callbacks that have this context @props.TodoStore.off null, null, this render: -> React.DOM.ul {}, @props.TodoStore.items.map (todoItem) -> # TODO: TodoItemComponent, which would bind to # `this.props.todoItem.on('change')` TodoItemComponent { todoItem: todoItem } module.exports = TodoListComponent

Bu Flux ve Omurga yaklaşımını kendi projelerime uyguladım ve bu kalıbı kullanmak için React uygulamamı yeniden tasarladığımda, neredeyse tüm çirkin bitler kayboldu. Biraz mucizeviydi: Birer birer, daha iyi bir yol aramak için dişlerimi gıcırdatmama neden olan kod parçalarının yerini mantıklı akış aldı. Ve Backbone'un bu kalıba entegre ettiği pürüzsüzlük dikkat çekicidir: Tek bir uygulamada onları bir araya getirmek için Backbone, Flux veya React ile savaşıyormuşum gibi hissetmiyorum.

Örnek Karışım

Bir bileşene her FluxBone Store eklediğinizde this.on(...) ve this.off(...) kodunu yazmak biraz eskiyebilir.

İşte son derece saf olmakla birlikte, hızlı bir şekilde yinelemeyi kesinlikle daha da kolaylaştıracak bir örnek React Mixin:

 # in FluxBoneMixin.coffee module.exports = (propName) -> componentDidMount: -> @props[propName].on "all", => @forceUpdate() , @ componentWillUnmount: -> @props[propName].off "all", => @forceUpdate() , @
 # in HelloComponent.coffee React = require("react") UserStore = require("./stores/UserStore") TodoStore = require("./stores/TodoStore") FluxBoneMixin = require("./FluxBoneMixin") MyComponent = React.createClass mixins: [ FluxBoneMixin("UserStore"), FluxBoneMixin("TodoStore"), ] render: -> React.DOM.div {}, "Hello, #{ @props.UserStore.get('name') }, you have #{ @props.TodoStore.length } things to do." React.renderComponent( MyComponent { UserStore: UserStore TodoStore: TodoStore } , document.body.querySelector(".main") )

Bir Web API'si ile Senkronizasyon

Orijinal Flux diyagramında, Web API ile yalnızca ActionCreators aracılığıyla etkileşime girersiniz; bu, eylemleri Göndericiye göndermeden önce sunucudan bir yanıt gerektirir. Bu bana hiç oturmadı; Değişikliklerden sunucudan önce ilk haberdar olanın Mağaza olması gerekmez mi?

Diyagramın bu kısmını tersine çevirmeyi seçiyorum: Mağazalar, Backbone'un sync() aracılığıyla doğrudan bir RESTful CRUD API ile etkileşime giriyor. Bu, en azından gerçek bir RESTful CRUD API'si ile çalışıyorsanız, harika bir şekilde uygundur.

Veri bütünlüğü sorunsuz bir şekilde korunur. Yeni bir özelliği .set() yaptığınızda, change olayı, yeni verileri iyimser bir şekilde görüntüleyen bir React yeniden oluşturmayı tetikler. Bunu sunucuya .save() yapmayı denediğinizde, request olayı bir yükleme simgesi görüntülemenizi sağlar. İşler yolunda gittiğinde, sync olayı, yükleme simgesini kaldırmanızı veya error olayı, işleri kırmızıya çevirmenizi sağlar. Burada ilham görebilirsiniz.

Ayrıca, ilk savunma katmanı için doğrulama (ve buna karşılık gelen bir invalid olay) ve sunucudan yeni bilgi çekmek için bir .fetch() yöntemi vardır.

Daha az standart görevler için ActionCreators aracılığıyla etkileşim kurmak daha mantıklı olabilir. Facebook'un çok fazla “sadece CRUD” yapmadığından şüpheleniyorum, bu durumda Mağazaları ilk sıraya koymamaları şaşırtıcı değil.

Çözüm

Facebook'taki Mühendislik ekipleri, React ile ön uç web'i ileriye taşımak için dikkate değer bir çalışma yaptı ve Flux'un tanıtımı, gerçekten ölçeklenen daha geniş bir mimariye bir göz atmanızı sağlıyor: sadece teknoloji açısından değil, mühendislik açısından da. Backbone'un akıllıca ve dikkatli kullanımı (bu eğitim örneğine göre) Flux'taki boşlukları doldurarak tek kişilik bağımsız mağazalardan büyük şirketlere kadar herkesin etkileyici uygulamalar oluşturmasını ve sürdürmesini inanılmaz derecede kolaylaştırabilir.

İlgili: React Components, UI Testini Nasıl Kolaylaştırıyor?