React高級篇(一)從Flux到Redux,react-redux(轉)

React框架本身只應用於View,如果基於MVC模式開發,還需要Model和Control層,這樣催生了Flux的產生,而Redux是基於Flux理念的一種解決方式。

從《React入門系列》可知,組建直接傳遞參數或者事件都需要props一層層代理,對於複雜組件,它可能嵌套的子組件非常多,層級也比較深,那麼,如果還採用props鏈條來維護組件通信或者數據共享,將非常困難,也不利於開發和維護。

Flux

Flux框架也是一種MVC框架,不同於傳統的MVC,它採用單向數據流,不允許Model和Control互相引用。Flux框架大致如下(來自網絡):

image.png

  • Actions: 驅動Dispatcher發起改變
  • Dispatcher: 負責分發動作(事件)
  • Store: 儲存數據,處理數據
  • View: 視圖部分

Dispatcher只會暴露一個函數-dispatch,接受Action爲參數,發起動作。如果需要增加新功能,不需要改變或者增加接口,只需增加Action類型。Dispatch的初始化和使用如下:

// Dispatcher.js
import {Dispatcher} from 'flux';
export default new Dispatcher();

// actions
import AppDispatcher from './Dispatcher.js';

export const increment = (number) => {
  AppDispatcher.dispatch({
    type: 'ADD',
    value: number
  });
};

Store 一般會繼承EventEmitter,實現事件監聽,發佈,卸載。需要將store註冊到Dispatcher實例上才能夠發揮作用。

Store可以直接修改對象,這點和Redux不同。

import AppDispatcher from './Dispatcher.js';

let value = 10;
const store = Object.assign({}, EventEmitter.prototype, {
  getValue: function() {  
    return value; 
  }

  emitChange: function() {
    this.emit('change');
  },

  addChangeListener: function(callback) {
    this.on('change', callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener('change', callback);
  }
});

store.dispatchToken = AppDispatcher.register((action) => {
  if (action.type === 'ADD') {
    value = value + action.value;
    store.emitChange();
  }
  // your codes
});
export default store;

view組件中的state應該與Flux store保持一致,如下:

function MyView extend Component {
  constructor(props){
    this.state = { count: store.getValue()}
  }

  // 聲明周期函數(組件加載和卸載),需要調用store的事件註冊函數,
  // 將處理組件state變化的函數設置爲註冊函數的回調方法
  componentDidMount() {
    store.addChangeListener(this.onChange);
  }

  componentWillUnmount() {
    store.removeChangeListener(this.onChange);
  }

  onChange() {
    const newCount = store.getValue();
    this.setState({count: newCount});
  }
  // 組件的事件函數,需要調用Action觸發狀態更新
  onClickIncrementButton() {
    Actions.increment(text);
  }
}

Flux的缺點爲:

  1. 一個應用可以擁有多個store,多個store直接可能有依賴關係(相互引用);
  2. Store封裝了數據和處理數據的邏輯。

針對Flux的不足,Redux框架出現。

Redux

相比Flux,Redux有如下兩個特點:

  1. 在整個應用只提供一個Store,它是一個扁平的樹形結構,一個節點狀態應該只屬於一個組件。
  2. 不允許修改數據。即不能修改老狀態,只能返回一個新狀態。

Redux數據流如下(來自網絡):

image.png

不同於 Flux ,Redux 並沒有 dispatcher 的概念(Store已經集成了dispatch方法,所以不需要Dispatcher)。它依賴純函數來替代事件處理器,這個純函數叫做Reducer。Reducer在Redux裏是個很重要的概念,其封裝了處理數據的邏輯。

 在計算機編程中,假如滿足下面這兩個句子的約束,一個函數可能被描述爲一個純函數:

1. 給出同樣的參數值,該函數總是求出同樣的結果。
該函數結果值不依賴任何隱藏信息或程序執行處理可能改變的狀態或在程序的兩個不同的執行。
2. 結果的求值不會促使任何可語義上可觀察的副作用或輸出。

簡單說,一個純函數,只要輸入相同,無論調用多少次,輸出都是一樣的。這就要求,絕不能修改輸入參數,因爲輸入參數有可能在其他地方用到。下面是一個簡單的Reducer對象:

export default (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {...state, value: action.value + 1};
    default:
      return state;
  }
}

把數據邏輯分離開後,Store就變得簡單了。它的構造函數需要一個reducer對象(每個組件對應一個reducer,通過combineReducers函數合併N個子reducer爲一個主reducer),初始化數據,和中間件(可選)。由於Store已經集成了dispatch方法,所以不需要Dispatcher。一個簡單的Store如下:

import {createStore, combineReducers} from 'redux';

const reducer = combineReducers({
    reducer: incrementReducer
});

export default createStore(reducer, {});

View層通過store.dispatch觸發動作:

onIncrement() {
   store.dispatch(Actions.increment(value));
}

組件 Context

看到這裏,可以發現Flux和Redux都需要顯性的在View裏面引入store, import store from './Store'。如果可以在一個應用中,只引入一次store,然後所有組件都可以訪問到,那該多好?!非常幸運,React提供了這樣的功能,即Context。

react context.png

Context就是“上下文環境”,讓一個數狀組件上所有組件都能訪問一個共有的對象。

讓頂層容器組件支持Context,那麼子組件都可以訪問到store,無需各自import。可以如下定義一個頂層組件:

import {PropTypes, Component} from 'react';

class Provider extends Component {
  //必須實現getChildContext方法
  getChildContext() {
    return {
      store: this.props.store
    };
  }

  render() {
    return this.props.children;
  }

}

Provider.propTypes = {
  store: PropTypes.object.isRequired
}
// 必須定義靜態屬性childContextTypes ,和getChildContext()對應
Provider.childContextTypes = {
  store: PropTypes.object
};

export default Provider;

在入口文件內使用頂層組件:

import React from 'react';
import ReactDOM from 'react-dom';

import store from './Store.js';
import Provider from './Provider.js';

ReactDOM.render(
  <Provider store={store}>
   ...
  </Provider>,
  document.getElementById('root')
);

所有子組件對象都可直接訪問到store對象:

const value =  this.context.store.getState();

react-redux

要聲明一點,Redux並不是專爲React開發的,它可以應用在任何框架上。針對React工程,可以使用react-redux庫幫助我們更快,更便捷得搭建Redux工程,讓代碼更加精簡。react-redux庫提供瞭如下功能:

  1. 把組件拆分爲容器組件和傻瓜組件,使用者只需要寫傻瓜組件;
  2. 使用React的Context提供了一個所有組件都可以直接訪問的Context,即react-redux Provider;

於是,我們不需要自己寫頂層組件了,只要導入react-redux的Provider,如下:

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import store from './Store.js';

ReactDOM.render(
  <Provider store={store}>
    ...
  </Provider>,
  document.getElementById('root')
);

Action和Store寫法不變,與Redux相同。

組件變得更加簡潔,如下:

function Counter({caption, onIncrement, onDecrement, value}) {
  return (
    <div>
      <button style={buttonStyle} onClick={onIncrement}>+</button>
      <span>{caption} count: {value}</span>
    </div>
  );
}

//store中狀態state到傻瓜組件屬性props的映射
function mapStateToProps(state, ownProps) {
  return {
    value: state[ownProps.caption]
  }
}

//傻瓜組件中用戶的每個動作,都轉換爲派送給store的動作
function mapDispatchToProps(dispatch, ownProps) {
  return {
    onIncrement: () => {
      dispatch(Actions.increment(ownProps.caption));
    }
  }
}
// connent函數:連接容器組件和傻瓜組件
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

可以看到,用了react-redux之後,代碼精簡不少,而且邏輯更加清晰。

小結

從Flux到Redux,再到react-redux,從這個簡短歷程中,我們可以看到框架設計上的演進,而redux + react-redux也是React開發萬家桶的標配。到了這裏,可以忘記Flux啦~



作者:娜姐聊前端
鏈接:https://www.jianshu.com/p/fe53e5fe189d
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章