Flux 架構入門教程(阮一峰)


轉載: http://www.ruanyifeng.com/blog/2016/01/flux.html

 

過去一年中,前端技術大發展,最耀眼的明星就是React

React 本身只涉及UI層,如果搭建大型應用,必須搭配一個前端框架。也就是說,你至少要學兩樣東西,才能基本滿足需要:React + 前端框架。

Facebook官方使用的是 Flux 框架。本文就介紹如何在 React 的基礎上,使用 Flux 組織代碼和安排內部邏輯,使得你的應用更易於開發和維護。

閱讀本文之前,我假設你已經掌握了 React 。如果還沒有,可以先看我寫的《React入門教程》。與以前一樣,本文的目標是使用最簡單的語言、最好懂的例子,讓你一看就會。

一、Flux 是什么?

簡單說,Flux 是一種架構思想,專門解決軟件的結構問題。它跟MVC 架構是同一類東西,但是更加簡單和清晰

Flux存在多種實現(至少15種),本文采用的是Facebook官方實現

二、安裝 Demo

為了便於講解,我寫了一個Demo

請先安裝一下。


$ git clone https://github.com/ruanyf/extremely-simple-flux-demo.git $ cd extremely-simple-flux-demo && npm install $ npm start 

然后,訪問 http://127.0.0.1:8080 。

你會看到一個按鈕。這就是我們的Demo。

三、基本概念

講解代碼之前,你需要知道一些 Flux 的基本概念。

首先,Flux將一個應用分成四個部分。

  • View: 視圖層
  • Action(動作):視圖層發出的消息(比如mouseClick)
  • Dispatcher(派發器):用來接收Actions、執行回調函數
  • Store(數據層):用來存放應用的狀態,一旦發生變動,就提醒Views要更新頁面

Flux 的最大特點,就是數據的"單向流動"。

  1. 用戶訪問 View
  2. View 發出用戶的 Action
  3. Dispatcher 收到 Action,要求 Store 進行相應的更新
  4. Store 更新后,發出一個"change"事件
  5. View 收到"change"事件后,更新頁面

上面過程中,數據總是"單向流動",任何相鄰的部分都不會發生數據的"雙向流動"。這保證了流程的清晰。

讀到這里,你可能感到一頭霧水,OK,這是正常的。接下來,我會詳細講解每一步。

四、View(第一部分)

請打開 Demo 的首頁index.jsx ,你會看到只加載了一個組件。

 // index.jsx var React = require('react'); var ReactDOM = require('react-dom'); var MyButtonController = require('./components/MyButtonController'); ReactDOM.render( <MyButtonController/>, document.querySelector('#example') ); 

上面代碼中,你可能注意到了,組件的名字不是 MyButton,而是 MyButtonController。這是為什么?

這里,我采用的是 React 的 controller view 模式。"controller view"組件只用來保存狀態,然后將其轉發給子組件。MyButtonController源碼很簡單。

 // components/MyButtonController.jsx var React = require('react'); var ButtonActions = require('../actions/ButtonActions'); var MyButton = require('./MyButton'); var MyButtonController = React.createClass({ createNewItem: function (event) { ButtonActions.addNewItem('new item'); }, render: function() { return <MyButton onClick={this.createNewItem} />; } }); module.exports = MyButtonController; 

上面代碼中,MyButtonController將參數傳給子組件MyButton。后者的源碼甚至更簡單。

 // components/MyButton.jsx var React = require('react'); var MyButton = function(props) { return <div> <button onClick={props.onClick}>New Item</button> </div>; }; module.exports = MyButton; 

上面代碼中,你可以看到MyButton是一個純組件(即不含有任何狀態),從而方便了測試和復用。這就是"controll view"模式的最大優點。

MyButton只有一個邏輯,就是一旦用戶點擊,就調用this.createNewItem 方法,向Dispatcher發出一個Action。

 // components/MyButtonController.jsx  // ... createNewItem: function (event) { ButtonActions.addNewItem('new item'); } 

上面代碼中,調用createNewItem方法,會觸發名為addNewItem的Action。

五、Action

每個Action都是一個對象,包含一個actionType屬性(說明動作的類型)和一些其他屬性(用來傳遞數據)。

在這個Demo里面,ButtonActions 對象用於存放所有的Action。

 // actions/ButtonActions.js var AppDispatcher = require('../dispatcher/AppDispatcher'); var ButtonActions = { addNewItem: function (text) { AppDispatcher.dispatch({ actionType: 'ADD_NEW_ITEM', text: text }); }, }; 

上面代碼中,ButtonActions.addNewItem方法使用AppDispatcher,把動作ADD_NEW_ITEM派發到Store。

六、Dispatcher

Dispatcher 的作用是將 Action 派發到 Store、。你可以把它看作一個路由器,負責在 View 和 Store 之間,建立 Action 的正確傳遞路線。注意,Dispatcher 只能有一個,而且是全局的。

Facebook官方的 Dispatcher 實現輸出一個類,你要寫一個AppDispatcher.js,生成 Dispatcher 實例。

 // dispatcher/AppDispatcher.js var Dispatcher = require('flux').Dispatcher; module.exports = new Dispatcher(); 

AppDispatcher.register()方法用來登記各種Action的回調函數。

 // dispatcher/AppDispatcher.js var ListStore = require('../stores/ListStore'); AppDispatcher.register(function (action) { switch(action.actionType) { case 'ADD_NEW_ITEM': ListStore.addNewItemHandler(action.text); ListStore.emitChange(); break; default:  // no op } }) 

上面代碼中,Dispatcher收到ADD_NEW_ITEM動作,就會執行回調函數,對ListStore進行操作。

記住,Dispatcher 只用來派發 Action,不應該有其他邏輯。

七、Store

Store 保存整個應用的狀態。它的角色有點像 MVC 架構之中的Model 。

在我們的 Demo 中,有一個ListStore,所有數據都存放在那里。

 // stores/ListStore.js var ListStore = { items: [], getAll: function() { return this.items; }, addNewItemHandler: function (text) { this.items.push(text); }, emitChange: function () { this.emit('change'); } }; module.exports = ListStore; 

上面代碼中,ListStore.items用來保存條目,ListStore.getAll()用來讀取所有條目,ListStore.emitChange()用來發出一個"change"事件。

由於 Store 需要在變動后向 View 發送"change"事件,因此它必須實現事件接口。

 // stores/ListStore.js var EventEmitter = require('events').EventEmitter; var assign = require('object-assign'); var ListStore = assign({}, EventEmitter.prototype, { items: [], getAll: function () { return this.items; }, addNewItemHandler: function (text) { this.items.push(text); }, emitChange: function () { this.emit('change'); }, addChangeListener: function(callback) { this.on('change', callback); }, removeChangeListener: function(callback) { this.removeListener('change', callback); } }); 

上面代碼中,ListStore繼承了EventEmitter.prototype,因此就能使用ListStore.on()ListStore.emit(),來監聽和觸發事件了。

Store 更新后(this.addNewItemHandler())發出事件(this.emitChange()),表明狀態已經改變。 View 監聽到這個事件,就可以查詢新的狀態,更新頁面了。

八、View (第二部分)

現在,我們再回過頭來修改 View ,讓它監聽 Store 的 change 事件。

 // components/MyButtonController.jsx var React = require('react'); var ListStore = require('../stores/ListStore'); var ButtonActions = require('../actions/ButtonActions'); var MyButton = require('./MyButton'); var MyButtonController = React.createClass({ getInitialState: function () { return { items: ListStore.getAll() }; }, componentDidMount: function() { ListStore.addChangeListener(this._onChange); }, componentWillUnmount: function() { ListStore.removeChangeListener(this._onChange); }, _onChange: function () { this.setState({ items: ListStore.getAll() }); }, createNewItem: function (event) { ButtonActions.addNewItem('new item'); }, render: function() { return <MyButton items={this.state.items} onClick={this.createNewItem} />; } }); 

上面代碼中,你可以看到當MyButtonController 發現 Store 發出 change 事件,就會調用 this._onChange 更新組件狀態,從而觸發重新渲染。

 // components/MyButton.jsx var React = require('react'); var MyButton = function(props) { var items = props.items; var itemHtml = items.map(function (listItem, i) { return <li key={i}>{listItem}</li>; }); return <div> <ul>{itemHtml}</ul> <button onClick={props.onClick}>New Item</button> </div>; }; module.exports = MyButton; 

九、致謝

本文受到了Andrew Ray 的文章《Flux For Stupid People》的啟發。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM