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

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

Redux的由來

2014年 Facebook 提出了 Flux 架構的概念和單向數據流管理的思想,并給出了管理狀態的基本數據流,但是隨著前端應用的復雜性指數級的提升,前端頁面需要管理的狀態也越來越多,于是出現了很多基于Flux基本的數據流概念和單向數據流思想的實現方式。2015年,Redux 出現,將 Flux 與函數式編程結合一起,很短時間內就成為了最熱門的前端架構。

在實際項目中,你應該有遇到過以下這樣情況的發生:

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

Redux工作原理

Redux 把自己標榜為一個“可預測的狀態容器 ”,它充分利用函數式的特性,讓整個實現更加優雅純粹,使用起來也更簡單。

Redux(oldState) => newState

Redux 可以看作是 Flux 的一次進化。Redux遵循以下三個基本原則:

  • 整個應用只有唯一一個可信數據源,也就是只有一個 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實例封裝

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

視圖層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 入門教程-阮一峰

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容