1. 程式人生 > >React之flux

React之flux

Flux:

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

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

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

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

Flux 的最大特點,就是資料的”單向流動

“。

                  

  • 使用者訪問 View
  • View 發出使用者的 Action
  • Dispatcher 收到 Action,要求 Store 進行相應的更新
  • Store 更新後,發出一個”change”事件
  • View 收到”change”事件後,更新頁面

上面過程中,資料總是”單向流動”,Action >Dispatcher > Store ,任何相鄰的部分都不會發生資料的”雙向流動”。這保證了流程的清晰

原文及程式碼來自於阮一峰的https://github.com/ruanyf/extremely-simple-flux-demo

本文基於cra腳手架建立,es6寫法。

View(第一部分)

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

import React, { Component } from 'react';
import MyButtonController from './components/MyButtonController';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <MyButtonController/>
      </div>
    );
  }
}
export default App;

MyButtonController的原始碼如下。

import React, { Component } from 'react';
import ListStore from '../stores/ListStore';
import ButtonActions from '../actions/ButtonActions';

import MyButton from './MyButton';

class MyButtonController extends Component {
  constructor(props){
      super(props);
      this.state = {
          items: ListStore.getAll()
      }
  }

  componentDidMount() {
    ListStore.addChangeListener(this._onChange);
  }

  componentWillUnmount() {
    ListStore.removeChangeListener(this._onChange);
  }

  _onChange = () => {
    this.setState({
      items: ListStore.getAll()
    });
  }

  createNewItem = (event) => {
    ButtonActions.addNewItem('new item');
  }

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

export default MyButtonController;

上面程式碼中,MyButtonController將引數傳給子元件MyButton。後者的原始碼甚至更簡單。

import React, { Component } from 'react';

class MyButton extends Component {
  render() {
    const { items } = this.props;
    const itemHtml = items.map(function (listItem, i) {
        return <li key={i}>{listItem}</li>;
      });
    return (
      <div>
        <ul>{itemHtml}</ul>
        <button onClick={this.props.onClick}>New Item</button>
      </div>
    );
  }
}

export default MyButton;

上面程式碼中,你可以看到MyButton是一個純元件(即不含有任何狀態),從而方便了測試和複用。這就是”controll view”模式的最大優點。 
MyButton只有一個邏輯,就是一旦使用者點選,就呼叫this.createNewItem 方法,向Dispatcher發出一個Action。

// components/MyButtonController.js

  // ...
   createNewItem = (event) => {
    ButtonActions.addNewItem('new item');
  }

上面程式碼中,呼叫createNewItem方法,會觸發名為addNewItem的Action。

Action

每個Action都是一個物件,包含一個actionType屬性(說明動作的型別)和一些其他屬性(用來傳遞資料)。 
在這個Demo裡面,ButtonActions 物件用於存放所有的Action。

// actions/ButtonActions.js
import AppDispatcher from '../dispatcher/AppDispatcher';

const ButtonActions = {
    addNewItem: function(text){
        AppDispatcher.dispatch({
            actionType: 'ADD_NEW_ITEM',
            text: text
        });
    }
}

export default ButtonActions;

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

Dispatcher

Dispatcher 的作用是將 Action 派發到 Store。你可以把它看作一個路由器,負責在 View 和 Store 之間,建立 Action 的正確傳遞路線。注意,Dispatcher 只能有一個,而且是全域性的。 
Facebook官方的 Dispatcher 實現輸出一個類,你要寫一個AppDispatcher.js,生成 Dispatcher 例項。

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

import { Dispatcher } from 'flux';
import ListStore from '../stores/ListStore';

const AppDispatcher = new Dispatcher();

AppDispatcher.register(function(action){
    switch(action.actionType){
        case 'ADD_NEW_ITEM':
            ListStore.addNewItemHandler(action.text);
            ListStore.emitChange();
            break;
        default:
    }
});

export default AppDispatcher;

上面程式碼中,Dispatcher收到ADD_NEW_ITEM動作,就會執行回撥函式,對ListStore進行操作。 
記住,Dispatcher 只用來派發 Action,不應該有其他邏輯。

Store

Store 儲存整個應用的狀態。它的角色有點像 MVC 架構之中的Model 。 
在我們的 Demo 中,有一個ListStore,所有資料都存放在那裡。

// stores/ListStore.js
var objectAssign = require('object-assign');
var EventEmitter = require('events').EventEmitter;

var ListStore = objectAssign({}, 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);
  }
});

module.exports = ListStore;

上面程式碼中,ListStore.items用來儲存條目,ListStore.getAll()用來讀取所有條目,ListStore.emitChange()用來發出一個”change“事件。 
由於 Store 需要在變動後向 View 傳送”change“事件,因此它必須實現事件介面。

上面程式碼中,ListStore繼承了EventEmitter.prototype,因此就能使用ListStore.on()ListStore.emit(),來監聽和觸發事件了。 
Store 更新後(this.addNewItemHandler())發出事件(this.emitChange()),表明狀態已經改變。 View 監聽到這個事件,就可以查詢新的狀態,更新頁面了。

View (第二部分)

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

// components/MyButtonController.js
import React, { Component } from 'react';
import ListStore from '../stores/ListStore';
import ButtonActions from '../actions/ButtonActions';

import MyButton from './MyButton';

class MyButtonController extends Component {
  constructor(props){
      super(props);
      this.state = {
          items: ListStore.getAll()
      }
  }

  componentDidMount() {
    ListStore.addChangeListener(this._onChange);
  }

  componentWillUnmount() {
    ListStore.removeChangeListener(this._onChange);
  }

  _onChange = () => {
    this.setState({
      items: ListStore.getAll()
    });
  }

  createNewItem = (event) => {
    ButtonActions.addNewItem('new item');
  }

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

export default MyButtonController;

上面程式碼中,你可以看到當MyButtonController 發現 Store 發出 change 事件,就會呼叫 this._onChange 更新元件狀態,從而觸發重新渲染。

// components/MyButton.jsx
import React, { Component } from 'react';

class MyButton extends Component {
  render() {
    const { items } = this.props;
    const itemHtml = items.map(function (listItem, i) {
        return <li key={i}>{listItem}</li>;
      });
    return (
      <div>
        <ul>{itemHtml}</ul>
        <button onClick={this.props.onClick}>New Item</button>
      </div>
    );
  }
}

export default MyButton;

 總結

  • 資料流是單向流動的。Store中給定初始資料,提供資料的getter()方法。
  • View上有互動,有資料更新就向Dispatcher發出一個Action。Action 中指定actionType和更新的資料。並通過AppDispatcher.dispatch(action)分發出去。並在元件渲染完成後監聽Store中資料的變化addChangeListener(),在元件銷燬時移除監聽的事件removeChangeListener().
  • Dispatcher登記各種Action的回撥函式,根據不同的actionType,呼叫ListStore的方法對資料進行更新,並觸發emitChange事件。
  • Store中自定義事件:addChangeListener(),removeChangeListener(),emitChange()實現事件介面。