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

React框架本身只應(yīng)用于View,如果基于MVC模式開發(fā),還需要Model和Control層,這樣催生了Flux的產(chǎn)生,而Redux是基于Flux理念的一種解決方式。

從《React入門系列》可知,組建直接傳遞參數(shù)或者事件都需要props一層層代理,對(duì)于復(fù)雜組件,它可能嵌套的子組件非常多,層級(jí)也比較深,那么,如果還采用props鏈條來維護(hù)組件通信或者數(shù)據(jù)共享,將非常困難,也不利于開發(fā)和維護(hù)。

Flux

Flux框架也是一種MVC框架,不同于傳統(tǒng)的MVC,它采用單向數(shù)據(jù)流,不允許Model和Control互相引用。Flux框架大致如下(來自網(wǎng)絡(luò)):

image.png
  • Actions: 驅(qū)動(dòng)Dispatcher發(fā)起改變
  • Dispatcher: 負(fù)責(zé)分發(fā)動(dòng)作(事件)
  • Store: 儲(chǔ)存數(shù)據(jù),處理數(shù)據(jù)
  • View: 視圖部分

Dispatcher只會(huì)暴露一個(gè)函數(shù)-dispatch,接受Action為參數(shù),發(fā)起動(dòng)作。如果需要增加新功能,不需要改變或者增加接口,只需增加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 一般會(huì)繼承EventEmitter,實(shí)現(xiàn)事件監(jiān)聽,發(fā)布,卸載。需要將store注冊(cè)到Dispatcher實(shí)例上才能夠發(fā)揮作用。

Store可以直接修改對(duì)象,這點(diǎn)和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應(yīng)該與Flux store保持一致,如下:

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

  // 聲明周期函數(shù)(組件加載和卸載),需要調(diào)用store的事件注冊(cè)函數(shù),
  // 將處理組件state變化的函數(shù)設(shè)置為注冊(cè)函數(shù)的回調(diào)方法
  componentDidMount() {
    store.addChangeListener(this.onChange);
  }

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

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

Flux的缺點(diǎn)為:

  1. 一個(gè)應(yīng)用可以擁有多個(gè)store,多個(gè)store直接可能有依賴關(guān)系(相互引用);
  2. Store封裝了數(shù)據(jù)和處理數(shù)據(jù)的邏輯。

針對(duì)Flux的不足,Redux框架出現(xiàn)。

Redux

相比Flux,Redux有如下兩個(gè)特點(diǎn):

  1. 在整個(gè)應(yīng)用只提供一個(gè)Store,它是一個(gè)扁平的樹形結(jié)構(gòu),一個(gè)節(jié)點(diǎn)狀態(tài)應(yīng)該只屬于一個(gè)組件。
  2. 不允許修改數(shù)據(jù)。即不能修改老狀態(tài),只能返回一個(gè)新狀態(tài)。

Redux數(shù)據(jù)流如下(來自網(wǎng)絡(luò)):

image.png

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

 在計(jì)算機(jī)編程中,假如滿足下面這兩個(gè)句子的約束,一個(gè)函數(shù)可能被描述為一個(gè)純函數(shù):

1. 給出同樣的參數(shù)值,該函數(shù)總是求出同樣的結(jié)果。
該函數(shù)結(jié)果值不依賴任何隱藏信息或程序執(zhí)行處理可能改變的狀態(tài)或在程序的兩個(gè)不同的執(zhí)行。
2. 結(jié)果的求值不會(huì)促使任何可語(yǔ)義上可觀察的副作用或輸出。

簡(jiǎn)單說,一個(gè)純函數(shù),只要輸入相同,無(wú)論調(diào)用多少次,輸出都是一樣的。這就要求,絕不能修改輸入?yún)?shù),因?yàn)檩斎雲(yún)?shù)有可能在其他地方用到。下面是一個(gè)簡(jiǎn)單的Reducer對(duì)象:

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

把數(shù)據(jù)邏輯分離開后,Store就變得簡(jiǎn)單了。它的構(gòu)造函數(shù)需要一個(gè)reducer對(duì)象(每個(gè)組件對(duì)應(yīng)一個(gè)reducer,通過combineReducers函數(shù)合并N個(gè)子reducer為一個(gè)主reducer),初始化數(shù)據(jù),和中間件(可選)。由于Store已經(jīng)集成了dispatch方法,所以不需要Dispatcher。一個(gè)簡(jiǎn)單的Store如下:

import {createStore, combineReducers} from 'redux';

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

export default createStore(reducer, {});

View層通過store.dispatch觸發(fā)動(dòng)作:

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

組件 Context

看到這里,可以發(fā)現(xiàn)Flux和Redux都需要顯性的在View里面引入store, import store from './Store'。如果可以在一個(gè)應(yīng)用中,只引入一次store,然后所有組件都可以訪問到,那該多好?!非常幸運(yùn),React提供了這樣的功能,即Context。

react context.png

Context就是“上下文環(huán)境”,讓一個(gè)數(shù)狀組件上所有組件都能訪問一個(gè)共有的對(duì)象。

讓頂層容器組件支持Context,那么子組件都可以訪問到store,無(wú)需各自import??梢匀缦露x一個(gè)頂層組件:

import {PropTypes, Component} from 'react';

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

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

}

Provider.propTypes = {
  store: PropTypes.object.isRequired
}
// 必須定義靜態(tài)屬性childContextTypes ,和getChildContext()對(duì)應(yīng)
Provider.childContextTypes = {
  store: PropTypes.object
};

export default Provider;

在入口文件內(nèi)使用頂層組件:

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')
);

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

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

react-redux

要聲明一點(diǎn),Redux并不是專為React開發(fā)的,它可以應(yīng)用在任何框架上。針對(duì)React工程,可以使用react-redux庫(kù)幫助我們更快,更便捷得搭建Redux工程,讓代碼更加精簡(jiǎn)。react-redux庫(kù)提供了如下功能:

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

于是,我們不需要自己寫頂層組件了,只要導(dǎo)入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相同。

組件變得更加簡(jiǎn)潔,如下:

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

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

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

可以看到,用了react-redux之后,代碼精簡(jiǎn)不少,而且邏輯更加清晰。

小結(jié)

從Flux到Redux,再到react-redux,從這個(gè)簡(jiǎn)短歷程中,我們可以看到框架設(shè)計(jì)上的演進(jìn),而redux + react-redux也是React開發(fā)萬(wàn)家桶的標(biāo)配。到了這里,可以忘記Flux啦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容