react入門之 Redux與flux的比較學習

前言

強烈推薦的學習資源:《深入淺出React和Redux》
此篇學習筆記記錄了對書中第三章節的學習心得
在學習之前,對react并沒有實踐經驗,有過Vuex開發經驗。但是讀完還是收獲很大,是用于前期理解Redux概念的一個很好的讀物。

總結

1、在學習之前,有過拿redux和vuex進行比較的念頭,但是在看完一遍redux之后,發現redux的單向數據流與vuex的雙向數據流思想相差還是蠻大的,所以就不比較了.....
2、在閱讀這一本書的redux章節當中,書中的講述順序十分的棒,從redux前身flux介紹起,逐步深入地過渡到redux,最后再介紹了成熟的react-redux庫,這種思路還是蠻不錯的。
3、感覺直接看概念,還是沒有vuex的思路清晰,因此做了一些擬人化的比喻,個人覺得比較受用...

目錄

  1. flux框架介紹
    1.1 flux四大元素
    1.2 flux的擬人化描述
    1.3 flux總結
  2. Redux框架介紹
    2.1 Redux基本原則
    2.2 Redux要素分析
    2.3 Redux的可改進之處
    2.4 搭上大神的順風車—————— react-redux

1、flux框架介紹

1.1 flux四大元素:

  • Dispatcher:根據注冊派發動作(action)
  • Store: 存儲數據,處理數據
  • Action:用于驅動Dispatcher
  • View: 用戶界面視圖
Dispatcher

是全局唯一的Dispatcher對象,關系網的中心

// AppDispatcher.js
// 完成聲明即可,后續無需改動
// 注冊action等事件主要在store中被調用完成
import {Dispatcher} from 'flux';
export default new Dispatcher ();
Store

注冊(register):把當前store注冊到Dispatcher下,加入dispatcher關系網
通過emit廣播、on掛載事件
store需要注冊到全局唯一的Dispatcher上才有效
flux核心部分:當register函數把一個回調函數注冊到Dispatcher后,所有派發給Dispatcher的action對象,都會傳遞到這個回調函數中

const counterValues = {
    'First': 0,
    'Second': 10,
    'Third': 30
}

// 聲明、生成store對象
const CounterStore = object.assign({}, EventEmitter.prototype, {
    getCounterValues: function() {
        return counterValues;
    },
    emitChange: function(){
        this.emit(CHANGE_EVENT);             // 廣播事件
    },
    addChangeListener: function(){
        this.on(CHANGE_EVENT, callback);     // 掛載事件
    },
    removeChangeListener: fucntion(){
        this.removeListener(CHANGE_EVENT, callback);      // 移除監聽
    }
})


// 把CounterStore注冊到全局唯一的Dispatcher上,register函數接受一個回調函數做參數
//注冊token(控制權令牌)
CounterStore.dispatchToken = AppDispathcer.register((action)=>{
    if(action.type === ActionTypes.INCREMENT){
        // do increment
        // 根據action對象,修改當前store中的counterValues變量
    } else if(action.type === ActionTypes.DECREMENT){
        // do decrement 
        // 根據action對象,修改當前store中的counterValues變量
    }
})   

使用waitFor()函數,通過dispatchToken的傳遞,實現同步調用,滿足多個store之間的相互依賴關系;
常用于獲取store中的最新鮮的數據

Action

代表一個動作的純數據對象
是js對象,且不自帶方法,用于驅動Dispatcher,來自用戶的請求
Action并不包含數據處理邏輯,而是調用函數,來創建對應的action對象

// ActionTypes.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
// action.js
import * as ActionTypes form './ActionTypes.js';
import AppDispatcher from './AppDispatcher.js'

export const increment = (counterCaption) => {
    AppDispatcher.dispatch({
        type: ActionTypes.INCREMENT,       // action對象類型
        counterCaption:counterCaption      // 用于標識發出action的來源(即頁面元素)
    })
}
View

用戶在界面中調用action

// react組件中的事件
onClickBtn() {
    // increment已經在對應的store中完成注冊,Dispactcher可識別
    Actions.increment(this, props.caption);
}

1.2 flux的擬人化描述

Dispatcher:
在十字路口中央指揮交通的交警,不會離開工作地點,是唯一的;
當有人督促我派發(dispatch)一下action,我就要打電話給我的小協警了,叫他趕緊來把這個家伙的事處理一下;

Store:
注冊:協警把自己的電話號碼給了十字路口的交警并告訴他:“發生交通事故,就打這個電話找我,我來處理現場”;
emit廣播事件:有人叫我去處理交通事故;
on掛載事件:如果有人叫我去處理交通事故,我要給出的反應;

view
路上的車主,一旦和別人的車撞上了,我要發出一個action(下車跑去找交警告訴交警我的事故屬于哪種類型)讓交警知道我撞車了,不然他不會理我的;

action
車主督促交警,趕緊把我的action派發(dispatch)出去,讓協警快來處理一下;


1.3 flux總結

flux的目的:糾正MVC框架的無法禁絕view與model通信的缺點;

flux的做法:store只有get方法,沒有set方法;因此view只能通過get獲取store狀態,不能修改狀態;如果想要修改store狀態,只能派發一個action給Dispatcher,由action中ActionTypes對應的store方法去修改store本身。

flux的缺點

  • store之間的依賴:需要建立依賴,需要token
  • 可以但是很難進行服務器端渲染(尚不理解)
  • store替換后,無法保持原有存儲狀態(指在開發中,store邏輯的修改無法熱加載)

2、Redux框架介紹

2.1 Redux基本原則:

  • 繼承Flux基本原則:單向數據流
  • 唯一數據源
  • 保持狀態只讀
  • 只有純函數能改變數據
1.唯一數據源

Flux:利用Dispatcher的waitFor方法,保證多個store之間的依賴與更新順序 ==> 數據冗余、應用復雜
Redux:所有state只保存在一個store中,整個應用只有一個store,狀態是一個樹形對象,每個組件使用狀態樹上的一部分數據

2.保持狀態只讀

狀態只可讀,不可直接修改
渲染原則:UI = render(state) <===> 界面只根據State進行渲染
與Flux相同,必須通過action對象才能修改store狀態

3.只有純函數能改變數據

純函數:指不依賴于且不改變它作用域之外的變量狀態的函數,也就是說函數的返回結果必須完全由傳入的參數決定

reducer(state, action)
reducer函數,接受兩個參數,第一個參數state是當前的狀態(最新的store.state),第二個參數action是接收到的action對象;reducer根據state與action的值產生并返回一個新的對象,返回的這一個對象用來組裝新狀態對象。


2.2 Redux要素分析

  • Store:存儲數據
  • Reducer:根據Action+state替換state,而非直接修改
  • Aciton:定義Action對象
  • Component:調用action
1.Store

在Flux中,Dispatcher的作用:把action對象分發給不同的、已注冊的store;
在redux中,只有一個store,是唯一的分發對象,因此將Dispatcher對象簡化為store對象中的一個函數dispatch。

使用createStore創建整個應用唯一的store,并且暴露到全局;
組件使用getOwnState函數用于從store中獲得狀態

import {createStore} from 'redux';
import reducer from './Reducer.js';

const counterValues = {
    'First': 0,
    'Second': 10,
    'Third': 20
}

const store = createStore(reducer, initValues);   // reducer表示更新狀態的reducer,initValues是狀態的初始值

export default store;

2.Reducer

reducer <===> redux與flux對state的操作差異
flux:直接修改state的值
redux:修改應用狀態,并不能直接修改狀態上的值,而是創建一個新的狀態對象返回給Redux,由Redux組裝新狀態對象。

// Flux版本的action
// Flux: 直接修改store狀態值
CounterStore.dispatchToken = AppDispatcher.register( (action) => {
    if(action.type === AcitonTypes.INCREMENT) {
        counterValues[action.counterCaption]++;
        CounterStore.emitChange();
    } else if(action.type === AcitonTypes.DECREMENT){
        counterValues[action.counterCaption]--;
        CounterStore.emitChange();
    }
})
// Redux版本的action
// 根據state與action的值產生并返回一個新的對象
// 返回的這一個對象用來組裝新狀態對象。  
function reducer (state,action) {
    const {counterCaption} = action;

    // 返回的對象是一個新的狀態樹
    // ... 擴展操作符
    // ...state表示擴展拆分state數據,去除了最頂層的樹結構,暴露二級節點
    // [counterCaption]: newData 則體現了redux狀態樹的設計
    // 使用組件的caption作為狀態樹的對應子數據字段
    switch (action.type) {
        case ActionTypes.INCREMENT: 
            return {...state, [counterCaption]: state[counterCaption] +1};
        case ActionTypes.DECREMENT: 
            return {...state, [counterCaption]: state[counterCaption] -1};
        default: 
            return state
    }
}
Action

flux:Action構造函數不返回什么,而是把構造的動作函數立刻通過調用dispatcher函數派發出去

// Flux之Action
export const increment = (counterCaption) => {
    AppDispatcher.dispatch({
        type: ActionTypes.INCREMENT,       // action對象類型
        counterCaption:counterCaption      // 用于標識發出action的來源(即頁面元素)
    })
}

reudx:每個Action構造函數返回一個action對象 == > 返回一個對象,把處理對象的工作交給調用者

// Redux之Action
export const increment = (counterCaption) => {
    return {
        type: ActionTypes.INCREMENT,       // action對象類型
        counterCaption:counterCaption      // 用于標識發出action的來源(即頁面元素)
    })
}



Component

用于聲明綁定派發action事件

onIncrement() {
    // 通過dispatch派發aciton
    store.dispatch(Actions.increment(this.props.caption));
}

render() {
    const value = this.state.value;
    const {caption} = this.props;
    return (
        <div>
            <button onclick={this.onIncrement}>+</button>
        </div>
    );
}

2.3 Redux的可改進之處

1. 組件功能單一化

當前組件具有兩個功能:1、派發Action,更新state樹;2、根據state與props渲染用戶界面
為了使組件專注于單一功能 ===> 拆分組件(容器組件、展示組件)
容器組件:外層組件,負責與store交互;
展示組件:內層組件,負責渲染界面,無狀態;

store 《====》 容器組件 ===(傳遞props)==》 展示組件 《====》 React界面

2.context全局訪問對象

理想目標
單個應用最好只導入一次全局Store,在最頂層React組件的位置;
為提高組件的復用性,其余組件應該避免直接導入Store;
為了滿足以上原則而出現的缺點:在一個多層嵌套組件結構中,當只有最里層組件需要使用store,為了將sotre從最外層傳到最里層,必須在所有中間組件中使用props逐級傳遞state。

Context:在樹狀組件中的所有組件都可訪問的一個共同對象,上下文環境
當上級組件宣稱自己支持context,并且提供一個函數來返回代表context的對象,所有子孫組件可在宣稱(import)后通過this.context訪問到這個共同的環境變量。

Provider類組件實現Context

// Provider類組件實現Context

import {PropTypes, Component} from 'react';

class Provider extends Component {
    getChildContext() {
        return {
            store: this.props.store
        };
    }
    
    render() {
        // 渲染子組件   props.children代表子組件
        return this.props.children;
    }
}

// 使Provider被React認可,成為一個Context提供者,必須指定childContextTypes屬性
Provider.childContextTypes = {
    store: PropTypes.object
};

Provider提供Context,暴露到所有子組件中

// Provider的實踐使用
// index.js  應用入口文件
import store from './Store.js'
import Provider from './Provider.js';

// ControlPanel是ReactDOM的頂層組件,現在被Provider組件包住后,Provider成為頂層組件
// Provider內層包裹的所有
ReactDOM.render (
    <Provider store={store}>
        <ControlPanel />
    </Provider>,
    document.getElementById('root')
)

在子組件中使用Context

// 第一步: 給組件類的contextTypes屬性賦值
CounterContainer.contextTypes = {
    store: PropTypes.object
}

// 第二步:在構造函數中用上第二個參數context
constructor(props, context) {
    // 寫法一:
    super(props, context);
    // 寫法二:
    super(...arguments);
}


// 第三步:通過this.context.store訪問store
getOwnState() {
    return {
        // [this.props.catption]用于獲取狀態樹中的某個二級狀態
        value: this.context.store.getState()[this.props.catption]
    }
}


2.4 搭上大神的順風車—————— react-redux

對于Redux的兩個改進在實現在實現上仍然具有一定的復雜性與機械性,因此已經有人創建了一個庫來幫我們完成這些工作(組件拆分與context)
react-redux庫兩大功能:
1、connect:連接容器組件與展示組件
2、Provider:我們不再需要自己實現Provider來獲取context,可以使用庫提供的Provider

connect

包含了兩個函數中執行:connectconnect返回函數
connect(mapStateToProps, mapDispatchToProps)的傳入參數是兩個映射函數,返回值是一個函數
connect函數作用:
1、向內傳遞state:把store上的狀態轉化為內層展示組件的props;
2、向外轉發Action:把內層展示組件中的用戶動作轉化為派送給store的動作

export default connect(mapStateToProps, mapDispatchToProps)(Counter);


// mapStateToProps函數,向內傳遞state => props
function mapStateToProps(state, ownProps) {
    return {
        value: state[ownProps.caption]
    }
}

// mapDispatchToProps函數,向外轉發Action
// ownProps屬性就是,直接傳遞給外層容器組件的props
function mapDispatchToProps(dispatch, ownProps) {
    return {
        onIncrement: () => {
            dispatch(Actions.increment(ownProps.caption));
        },
        onDecrement: () => {
            dispatch(Actions.decrement(ownProps.captions))
        }
    }
}

Provider

import {Provider} from 'react-redux';

react-redux庫的提供的Provider幾乎相同,但更加嚴謹;
react-redux庫要求store必須是一個包含以下三個函數的object:

  • subscribe
  • dispatch
  • getState

react-redux還提供了componentWillReceiveProps鉤子,用于每次重新渲染時調用

redux總結 & 與flux的比較

  1. 全局唯一數據源,store
  2. reducer替換狀態樹,而非直接修改值
  3. Provider優化props傳遞
  4. 組件拆解,功能單一化

react項目常見組織結構

  • ReactApp
    |---reducers // 所有的reducer
    |---actions // 所有的action構造函數
    |---components // 所有的展示組件
    |---containers // 所有的容器組件
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容