Flux 架構入門教程

jopen 8年前發布 | 15K 次閱讀 Facebook 前端技術

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

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

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

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

一、Flux 是什么?

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

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

二、安裝 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 MyButton = React.createClass({
  createNewItem: function (event) {
    ButtonActions.addNewItem('new item');
  },

  render: function() {
    return <MyButton
      onClick={this.createNewItem}
    />;
  }
});

module.exports = MyButton;

上面代碼中, 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 只能有一個,而且是全局的。

非死book官方的 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》 的啟發。

(完)

來自: http://www.ruanyifeng.com/blog/2016/01/flux.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!