React.js 素晴らしい図書館です。 Pythonをスライスして以来、それが最良のように思えることもあります。ただし、Reactはフロントエンドアプリケーションスタックの一部にすぎません。データと状態の管理に関しては、提供できるものはそれほど多くありません。
ReactのメーカーであるFacebookは いくつかのガイダンスを提供しました の形でそこに フラックス 。 Fluxは、Reactビュー、アクションディスパッチャー、およびストアを使用して一方向のデータフローを中心に構築された「アプリケーションアーキテクチャ」(フレームワークではありません)です。 Fluxパターンは、イベント制御の重要な原則を具体化することによっていくつかの主要な問題を解決します。これにより、Reactアプリケーションの推論、開発、および保守がはるかに簡単になります。
ここでは、制御フローの基本的なFluxの例を紹介し、ストアに欠けているものと、バックボーンモデルとコレクションを使用して「Flux準拠」の方法でギャップを埋める方法について説明します。
(注:便宜上、簡潔にするために、例ではCoffeeScriptを使用しています。CoffeeScript以外の開発者は、従うことができ、例を擬似コードとして扱うことができます。)
背骨 は、ビュー、モデル、コレクション、およびルートを含む、優れた、十分に吟味された小さなライブラリです。それは デファクト 構造化フロントエンドアプリケーションの標準ライブラリであり、2013年にReactアプリが導入されて以来、Reactアプリとペアになっています。これまでのFacebook.com以外のReactのほとんどの例には、Backboneがタンデムで使用されているという言及が含まれています。
残念ながら、Reactのビュー以外のアプリケーションフロー全体を処理するためにバックボーンだけに頼ると、不幸な問題が発生します。私が最初にReact-Backboneアプリケーションコードの作業を始めたとき、私が持っていた「複雑なイベントチェーン」 について読む ヒドラのような頭を育てるのにそれほど時間はかかりませんでした。 UIからモデルにイベントを送信し、次にあるモデルから別のモデルにイベントを送信してから再度送信すると、誰が誰を、どの順序で、なぜ変更したかを追跡するのが困難になります。
このFluxチュートリアルでは、Fluxパターンがこれらの問題を驚くほど簡単かつシンプルに処理する方法を示します。
Fluxのスローガンは「一方向のデータフロー」です。これはからの便利な図です フラックスドキュメント そのフローがどのように見えるかを示します。
重要なのは、React --> Dispatcher --> Stores --> React
からデータが流れるということです。
各主要コンポーネントとは何か、およびそれらがどのように接続されているかを見てみましょう。
ドキュメントには、この重要な警告も記載されています。
Fluxはフレームワークというよりもパターンであり、強い依存関係はありません。ただし、ストアのベースとしてEventEmitterを使用し、ビューのReactを使用することがよくあります。他の場所ではすぐに利用できないフラックスの1つは、ディスパッチャーです。このモジュールは、Fluxツールボックスを完成させるためにここから入手できます。
したがって、Fluxには3つのコンポーネントがあります。
React = require('react')
)Dispatcher = require('flux').Dispatcher
)EventEmitter = require('events').EventEmitter
)Backbone = require('backbone')
)Reactについては、Angularよりも非常に好むと言う以外に多くのことが書かれているため、ここでは説明しません。私はほとんど感じません 混乱している Angularとは異なり、Reactコードを作成する場合、もちろん意見は異なります。
Flux Dispatcherは、ストアを変更するすべてのイベントが処理される単一の場所です。それを使用するには、各ストアがありますregister
すべてのイベントを処理するための単一のコールバック。そうすれば、ストアを変更するときはいつでもdispatch
イベント。
c ++の高度なチュートリアル
Reactのように、Dispatcherは、うまく実装された良いアイデアだと思います。例として、ユーザーがToDoリストに項目を追加できるアプリには次のものがあります。
# 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!'
これにより、次の2つの質問に簡単に答えることができます。
MyStore
を変更するすべてのイベントは何ですか?switch
でケースを確認してくださいMyStore.dispatchCallback
のステートメント。actionType
を検索するだけです。これは、たとえばMyModel.set
を探すよりもはるかに簡単です。 そして MyModel.save
そして MyCollection.add
など、これらの基本的な質問への回答を追跡することは非常に速く非常に困難になります。
Dispatcherでは、waitFor
を使用して、コールバックを単純な同期方式で順次実行することもできます。例えば:
# 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
実際には、waitFor
を使用しなくても、ディスパッチャーを使用してストアを変更すると、コードがどれほどクリーンになるかを見てショックを受けました。
cまたはc ++を学ぶ
したがって、データフロー に ディスパッチャを介して保存します。とった。しかし、データはストアからビューにどのように流れますか(つまり、React)?で述べたように フラックスドキュメント :
[]ビューは、依存するストアによってブロードキャストされるイベントをリッスンします。
いいでしょうストアにコールバックを登録したのと同じように、ビュー(Reactコンポーネント)にコールバックを登録します。 Reactに再-render
を指示しますprops
を介して渡されたストアで変更が発生したときはいつでも。
例えば:
# 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
驚くばかり!
では、どのようにそれを放出するのでしょうか'change'
イベント?さて、FluxはEventEmitter
の使用をお勧めします。公式の例から:
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...
キモい!シンプルなストアが必要になるたびに、自分ですべてを書く必要がありますか?表示したい情報があるたびに使用することになっているのはどれですか?より良い方法が必要です!
バックボーンのモデルとコレクションには、FluxのEventEmitterベースのストアが行っているように見えるすべてのものがすでに含まれています。
生のEventEmitterを使用するように指示することにより、Fluxは、ストアを作成するたびに、Backboneのモデルとコレクションの50〜75%を再作成することをお勧めします。店舗にEventEmitterを使用することは、Express.jsなどの十分に構築されたマイクロフレームワークがすでに存在し、すべての基本と定型文を処理する場合に、サーバーに裸のNode.jsを使用することに似ています。
Express.jsがNode.js上に構築されているように、BackboneのモデルとコレクションはEventEmitter上に構築されています。そして、それはあなたがほとんどいつも必要とするすべてのものを持っています:バックボーンはchange
を放出しますイベントがあり、クエリメソッド、ゲッター、セッターなどすべてがあります。さらに、バックボーンの ジェレミー・アシュケナス と彼の軍隊 230人の寄稿者 私ができると思われるよりも、これらすべてのことではるかに優れた仕事をしました。
このバックボーンチュートリアルの例として、MessageStoreの例を上からに変換しました バックボーンバージョン 。
客観的にコードが少なく(作業を複製する必要がない)、主観的により明確で簡潔です(たとえば、this.add(message)
の代わりに_messages[message.id] = message
)。
それでは、バックボーンをストアに使用しましょう。
このチュートリアルは、私が誇らしげに吹き替えたアプローチの基礎です FluxBone 、Backbone forStoresを使用したFluxアーキテクチャ。 FluxBoneアーキテクチャの基本的なパターンは次のとおりです。
.set()
はありません)。代わりに、コンポーネントはアクションをディスパッチャーにディスパッチします。
バックボーンとフラックスの例を使用して、その各部分を順番に見ていきましょう。
1.ストアは、ディスパッチャーにコールバックを登録した、インスタンス化されたバックボーンモデルまたはコレクションです。
# 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.コンポーネント 決して ストアを直接変更します(たとえば、.set()
はありません)。代わりに、コンポーネントはアクションをディスパッチャーにディスパッチします。
# 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.コンポーネントはストアにクエリを実行し、それらのイベントにバインドして更新をトリガーします。
# 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
私はこのフラックスとバックボーンのアプローチを自分のプロジェクトに適用しました。このパターンを使用するようにReactアプリケーションを再構築すると、ほとんどすべての醜いビットが消えました。それは少し奇跡的でした。より良い方法を探して歯を食いしばったコードの断片が、賢明な流れに置き換えられました。そして、Backboneがこのパターンに統合されているように見える滑らかさは注目に値します。単一のアプリケーションにそれらを合わせるために、Backbone、Flux、またはReactと戦っているような気がしません。
this.on(...)
を書くおよびthis.off(...)
FluxBoneストアをコンポーネントに追加するたびに、コードが少し古くなる可能性があります。
これは、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') )
元のFluxダイアグラムでは、ActionCreatorを介してのみWeb APIと対話します。これは、アクションをDispatcherに送信する前にサーバーからの応答を必要とします。それは私には決して正しくありませんでした。サーバーの前に、ストアが変更について最初に知っているべきではありませんか?
図のその部分を裏返すことにしました。ストアは、バックボーンのsync()
を介してRESTfulCRUDAPIと直接対話します。これは、少なくとも実際のRESTful CRUD APIを使用している場合は、非常に便利です。
データの整合性は問題なく維持されます。あなたが.set()
するとき新しいプロパティ、change
イベントはReactの再レンダリングをトリガーし、新しいデータを楽観的に表示します。 .save()
をしようとするとサーバーに送信しますrequest
イベントは、読み込みアイコンを表示することを通知します。物事が進むと、sync
イベントは、読み込みアイコン、またはerror
を削除することを通知しますイベントはあなたに物事を赤くすることを知らせます。あなたはインスピレーションを見ることができます ここに 。
防御の第1層の検証(および対応するinvalid
イベント)と.fetch()
もありますサーバーから新しい情報を取得する方法。
ウェブサイトからメタマスクに接続する方法
標準的でないタスクの場合、ActionCreatorsを介した対話の方が理にかなっている場合があります。 Facebookは「単なるCRUD」をあまり行っていないのではないかと思います。その場合、Facebookがストアを最優先しないのは当然のことです。
Facebookのエンジニアリングチームは、 フロントエンドWebを前方にプッシュ と React 、およびFluxの導入により、テクノロジーだけでなくエンジニアリングの観点からも、真に拡張可能なより広範なアーキテクチャを垣間見ることができます。バックボーンを巧妙かつ注意深く使用すると(このチュートリアルの例による)、Fluxのギャップを埋めることができ、個人のインディーショップから大企業まで、誰でも印象的なアプリケーションを作成して維持することが驚くほど簡単になります。
関連: ReactコンポーネントがUIテストを容易にする方法