數據流架構學習筆記(二)-Redux

初期參加工做開發項目時,使用React Native + Flux進行手機應用跨平臺開發,在上一篇博文中數據流架構學習筆記(一)-Flux 對Flux作了一次總結,本文是我對數據流管理架構學習總結的第二篇數據流架構學習筆記(二)-Redux,是我在工做過程當中對項目使用Redux進行重構狀態管理和數據流的學習記錄。javascript

Redux的由來

2014年 Facebook 提出了 Flux 架構的概念和單向數據流管理的思想,並給出了管理狀態的基本數據流,可是隨着前端應用的複雜性指數級的提高,前端頁面須要管理的狀態也愈來愈多,因而出現了不少基於Flux基本的數據流概念和單向數據流思想的實現方式。2015年,Redux 出現,將 Flux 與函數式編程結合一塊兒,很短期內就成爲了最熱門的前端架構。html

在實際項目中,你應該有遇到過如下這樣狀況的發生:前端

  • 在debug項目進行問題查找時,複雜的數據刷新頁面時,因爲一些不規範的監聽和觀察者機制使用或過多使用React中this.setState進行渲染頁面,因爲是異步進行數據渲染頁面,常常沒法判斷本次形成是由於什麼數據狀態改變而形成頁面渲染,並且更可惡的是此時你沒法知道當前狀態下,你的App實際的數據狀態是如何的,就是說,你沒法知道你App當前的全部數據是多少,而你一樣也沒法快速預測接下來你的App會如何變化。使用Redux就能夠很好的解決這個問題。
  • 一樣的若是你還在項目中進行模塊劃分,組件化開發,使用Redux能夠快速將你的模塊組件進行併入項目和拆分重組。

Redux工做原理

Redux 把本身標榜爲一個「可預測的狀態容器 」,它充分利用函數式的特性,讓整個實現更加優雅純粹,使用起來也更簡單。java

Redux(oldState) => newState複製代碼

Redux 能夠看做是 Flux 的一次進化。Redux遵循如下三個基本原則:react

  • 整個應用只有惟一一個可信數據源,也就是隻有一個 Store
  • State 只能經過觸發 Action 來更改
  • State 的更改必須寫成純函數,也就是每次更改老是返回一個新的 State,在 Redux 裏這種函數稱爲 Reducer
View 觸發數據更新 —> Actions 將數據傳遞到 Store —> Store 更新 state —> 更新 View。複製代碼

Redux 中整個應用的狀態存儲在一顆 object tree 中,對應一個惟一的 Store,而且 state 是隻讀的,使用純函數 reducer 來更新 state 會生成一個新的 state 而不是直接修改原來的。編程

Redux 經過以上約束讓 state 的變化可預測。redux

若是沒法理解這些概念,建議先學習Redux官方文檔,再來查看他人的博客和使用方式,才能更快的使用。promise

Redux實例封裝

這裏以React Native實際項目登陸部分展現如何將Redux應用到React Native開發中進行數據管理,在實際架構項目時,每一個人有各自的編碼習慣,於是,雖然一樣是Redux,可是在各部分代碼寫法老是有所不同,而實際項目中用起來的寫法也是不同的,可是思想整體上是同樣的,不要拘泥於代碼的寫法,代碼只是做爲參考和總結,應該理解寫法的目的思想和如何體現Redux的融入和使用。bash

視圖層View

登陸頁:網絡

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import LoginAction from '../../actions/loginAction';
...
class Login extends Component {
    ...
    _doLogin = () => {
        const param = {
          uid: this.state.uid,
          pwd: this.state.pwd
        };

        this.props.actions.doLogin(param)
          .then(() => {
            const { navigation, login } = this.props;
            if (login.status === 'done' && navigation) {
              navigation.resetRouteTo('TabBar', { title: '首頁', selectedTab: 'home' });
            } else {
              Alert.alert(
                '提示',
                login.message
              );
            }
          });
      };
    ...
}
...
const mapStateToProps = (state) => {
  return {
    login: state.loginReducer
  };
};
const mapDispatchToProps = dispatch => {
  return ({
    actions: bindActionCreators({ ...LoginAction }, dispatch)
  });
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);複製代碼

簡單說View層主要做用就是響應用戶的操做,而實際在代碼中咱們主要的做用就是觸發Action,如代碼中調用this.props.actions.doLogin()函數,在this.props會存在actions屬性是因爲在最後使用bindActionCreators方法將對應的LoginAction綁定至頁面組件Login中,這樣形成我在View層只會作調用action的操做,不會直接使用dispatch進行消息分發。這樣就完成了View -> Actions的過程。

行爲Action

const _loginSuccess = (data) => {//eslint-disable-line
  return {
    type: ActionTypes.LOGIN_SUCCESS,
    payload: {
      user: data.uid
    }
  };
};

const _loginFailed = (error) => {
  return {
    type: ActionTypes.FAIL,
    payload: {
      message: error.message
    }
  };
};

const _doLogin = (url, param) => dispatch => {
  dispatch(CommonAction.showLoading());
  return Fetcher.postQsBodyFetch(url, param)
      .then((response) => {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginSuccess(param, response));
      }).catch((error) => {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginFailed(error));
      });
};

const LoginAction = {
  doLogin: (param) => _doLogin(NetLink.login, param),
  loginSuccess: (data) => _loginSuccess(data),
  loginFailed: (error) => _loginFailed(error),
};複製代碼

Action一般都是在進行網絡層調用、請求數據和分發數據,因在View層使用了bindActionCreators方法和組件綁定後,將會直接獲取View層組件dispatch屬性方法,使得在Action的純函數中在數據返回後調用dispatch()進行數據分發。這樣就完成了Actions -> Reducer的過程。

Reducer

import ActionType from '../constants/actionType';

const initialState = {
  status: 'init',
  user: '',
  message: null
};

const loginReducer = (state = initialState, action) => {
  switch (action.type) {
    case ActionType.LOGIN_SUCCESS:
      return Object.assign({}, state, {
        status: 'done',
        user: action.payload.user,
      });
    case ActionType.FAIL:
      return Object.assign({}, state, {
        status: 'fail',
        message: action.payload.message,
      });
    default:
      return state;
  }
};複製代碼

Reducer相似原來Flux的store,做爲數據倉庫來源,這裏將會收到來自調用dipatch()後得到的消息,並進行處理和保存,並在及時更新數據後,經過redux的組件綁定,自動反饋至頁面組件中進行數據更新和異步渲染,而在這裏你應該return一個全新的對象,redux才能知道你是更新了當前組件關聯的reducer,到了這一步你應該會產生疑問,數據狀態是如何反饋至View,而你寫的普普統統的Action和Reducer等js文件是如何關聯上你的組件和應用。

綁定和引入

入口組件Root.js:

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import Fetcher from './network/fetcher';
import Main from './containers/mainContainer';
import rootReducer from './reducers/rootReducer';

const middlewares = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
const createLogger = require('redux-logger');

if (process.env.NODE_ENV === 'development') {
  const logger = createLogger();
  middlewares.push(logger);
}

function configureStore(initialState) {
  const store = createStoreWithMiddleware(rootReducer, initialState);
  return store;
}

const store = configureStore();

export default class Root extends Component {
  constructor(props) {
    super(props);
    Fetcher.initNetworkState();
  }

  componentWillUnmount() {
    Fetcher.removeNetworkStateListener();
  }

  render() {
    return (
      <Provider store={store}> <Main {...this.props} /> </Provider> ); } }複製代碼

其中rootReducer.js:

import { combineReducers } from 'redux';
import LoginReducer from './loginReducer';
...

const rootReducer = combineReducers({
  loginReducer: LoginReducer,
  ...
});

export default rootReducer;複製代碼

先使用combineReducer將全部的reducer合併成一個rootReducer,使用rootReducer,在接着開始經過createStore方法建立了store對象,經過redux提供的Provider組件直接將store對象綁定至實際的View組件上,這樣就完成了

View 觸發數據更新 —> Actions 將數據傳遞到 Store —> Store 更新 state —> 更新 View。複製代碼

關於其中的applyMiddlewarebindActionCreators等等方法是關於異步actions、異步數據流、Middleware的進階知識。具體內容建議查看Redux官方文檔瞭解相關內容。如下Redux進階也將會大概講解他們的使用和爲何使用。

Redux進階

使用redux-thunk和redux-logger框架,並使用applyMiddleware和Middleware來增強Redux的使用,使Redux更增強大、規範和合理。其中redux-logger框架簡單理解就是添加一個Redux日誌打印和處理框架,開發者無需知道他如何規範打印出Redux的dispatch日誌的,可是在開發時,用於debug等是頗有用的工具。redux-thunk屬於處理異步Actions和異步數據流的框架。

異步Actions和異步數據流

什麼是異步Actions和異步數據流,簡單來講,就是網絡請求來控制的Actions。如App中你點擊一個按鈕當即發出了一個dispatch(), 這是你對App控制做出的dispatch(), 這叫作同步Actions,而異步Actions並非你控制的,如網絡請求成功或失敗後,纔會發出一個dispatch(),這就是異步Actions,你沒法知道這個dispatch()是什麼時間作出的操做,也不知道你發出的是成功的dispatch()或是失敗的dispatch()。

如我loginAction中方法,如今應該就能很好的理解這個方法這樣寫的原理:

const _doLogin = (url, param) => dispatch => {
  dispatch(CommonAction.showLoading());
  return Fetcher.postQsBodyFetch(url, param)
      .then((response) => {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginSuccess(param, response));
      }).catch((error) => {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginFailed(error));
      });
};複製代碼

Middleware

middleware翻譯成中文意思中間件,很貼切也很容易理解,像redux-thunk 或 redux-promise就能夠叫作中間件,若是你想使用這些中間件,就須要使用applyMiddleware等等相關方法爲你的項目添加上這些框架。使項目使用redux更增強大和規範。

像redux-thunk 或 redux-promise 這樣支持異步的 middleware 都包裝了 store 的 dispatch() 方法,以此來讓你 dispatch 一些除了 action 之外的其餘內容,例如:函數或者 Promise。你所使用的任何 middleware 均可以以本身的方式解析你 dispatch 的任何內容,並繼續傳遞 actions 給下一個 middleware。好比,支持 Promise 的 middleware 可以攔截 Promise,而後爲每一個 Promise 異步地 dispatch 一對 begin/end actions。

當 middleware 鏈中的最後一個 middleware 開始 dispatch action 時,這個 action 必須是一個普通對象。這是 同步式的 Redux 數據流 開始的地方(譯註:這裏應該是指,你可使用任意多異步的 middleware 去作你想作的事情,可是須要使用普通對象做爲最後一個被 dispatch 的 action ,來將處理流程帶回同步方式)。

middleware 能夠完成包括異步 API 調用在內的各類事情,瞭解它的演化過程是一件至關重要的事。而他們是如何演化過來的,並如何增強你的應用的,這裏再也不具體說明。

總結

Redux很強大,也相對複雜。簡單的項目也許並不須要使用到,可是若是你的項目愈來愈大,數據愈來愈複雜,Redux將會使你項目更加規範和健壯。在項目中使用規範的框架架構,是一件很是重要的事情,你能夠爲你的項目使用架構,這是一件頗有趣的事情。

文章很長,Redux也很複雜,文中若有不對請告知,我會及時改正。一塊兒進步和學習。謝謝!

參考

Redux 中文文檔

Redux 入門教程-阮一峯

相關文章
相關標籤/搜索