react-redux用法詳解

一、相關依賴包

redux 路由是react全家桶【react+redux + router】中的一員,在項目中用於組件狀態的管理,使組件傳值通信更加方便,統一管理。Redux中文文檔地址 http://cn.redux.js.org/

下面我們看使用redux時相關的包。

主要是redux和react-redux這兩個包

redux是redux狀態管理的核心包提供了創建store、統一管理reducer、綁定action、中間件集成及管理等組件。

react-redux是 使react組件能夠使用redux提供的狀態管理store,包含兩個組件包Provider和connect。

在react項目中一般兩者搭配使用。

1、安裝命令

 npm install redux --save --dev-save 
 npm install react-redux --save --dev-save 
 npm install redux-thunk --save --dev-save 
 npm install redux-saga --save --dev-save 
 

2、涉及到的相關組件包

import { createStore,combineReducers, applyMiddleware, compose, bindActionCreators,Action }
from 'redux';
import { Provider, connect} from 'react-redux';
import thunkMiddleware from 'redux-thunk'; // action的異步方案選型中間件
import createSagaMiddleware from 'redux-saga'; // action的異步方案選型中間件

3、redux在系統中結構和工作過程

redux: 是一個與react相同級別的獨立的狀態管理模塊。 主要功能是創建store狀態管理容器並且爲store綁定改變store中內容的reducer和爲store的派發請求綁定異步請求的中間件[thunk或saga]對組件請求進行攔截處理。

import { createStore, combineReducers, applyMiddleware } from "redux";
----------------------------thunk中間件-----------------------------------------
import thunkMiddleware from "redux-thunk";
export interface rootState {
  readonly HistoryReducer: HistoryState;
}
//combineReducers 主要針對多reducer處理
const rootReducer = combineReducers<rootState>({ HistoryReducer });
const defaultMiddlewares = [thunkMiddleware];
const store = createStore(rootReducer, applyMiddleware(...defaultMiddlewares));

-----------------------------saga中間件----------------------------------------------
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducers'
import rootSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
sagaMiddleware.run(rootSaga)

react-redux: 是react和redux的粘合劑,使組件能夠使用redux的store進行狀態管理。
Provider 使用redux創建的store 包裹整個組件樹,使所有被包裹的組件都能夠通過props使用store容器中的內容,方便了組件之間的傳值。

import { Provider } from "react-redux";
import AppComponent from './AppComponent'
function App() {
  return (
  	// Provider將整個組件進行包裹
    <Provider store={store}>
     	<AppComponent />
    </Provider>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

connect 將普通UI組件使用 react-redux提供的connect封裝爲容器組件,這樣普通的UI組件就和redux創建的store狀態管理容器就綁定連接起來了,組件可以從store中獲取屬性值,可以發送action觸發reducer更新store中的內容。

這個函數組件的文檔https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md#connect

connect(mapStateToProps,mapDispatchToProps,mergeProps,options)函數參數及類型
mapStateToProps?: Function
mapDispatchToProps?: Function | Object
mergeProps?: Function
options?: Object
export class componentApp extends React.Component<IProps, Istate>{
  componentWillMount(){}
  componentDidMount() {
    this.props.fetchFirstQuarter();
  }
 render() {   
    const { data } = this.props;
    return (
      <div><List
          grid={{ gutter: 16, column: 4 }}
          dataSource={data}
          renderItem={item => (
            <List.Item>
              <Card title={item.title}>{item.content}</Card>
            </List.Item>
          )}
        /></div>
    );
  }
}

const mapstateToprops = (state: rootState) => {
   return ({
    data: state.HistoryReducer.firstQuarter
  })
};
const mapDispatchToProps = {
  fetchFirstQuarter
};
export default connect(mapstateToprops, mapDispatchToProps)(componentApp);
組件執行過程

[外鏈圖片轉存失敗(img-z7Xh9hIK-1568965813909)(C:\Users\sunxingba\AppData\Roaming\Typora\typora-user-images\1568711987314.png)]

當通過路由訪問到容器組件之後,容器組件先執行connect中的第一個參數的mapstateToprops,從redux創建的store狀態容器中獲取一次組件映射的屬性值,再進入組件的初始化階段執行constructor方法,掛載階段執行 componentWillMount-> render -> componentDidMount。掛載完成後組件就加載完畢。如果不進行頁面數據的初始加載組件的執行過程就完畢了。

順序:
mapstateToprops -> constructor -> componentWillMount -> render -> componentDidMount-> componentWillUnmount[切換到其他組件時執行該組件卸載]

一般我們會在頁面進行加載數據,這時就需要調用執行connect中的第二個參數的mapDispatchToProps中的函數,進行派發action【這一步會涉及到異步的中間件的使用】執行reducer更新redux創建的store中的內容。【注意:調用函數的位置可以在componentWillMount渲染完成之前和componentDidMount渲染完成之後這兩個生命週期函數中。這兩個方法中的執行更新store不會影響組件的掛載過程。調用函數的執行是對組件屬性進行更新的過程,屬於組件的更新階段。一般推薦在componentDidMount中進行加載數據。】,當在componentDidMount中調用了更新數據的方法後,組件再次執行connect的第一個參數的mapstateToprops,從redux創建的store狀態容器中再獲取一次最新的組件映射的屬性值。再進入組件的更新階段執行 componentWillReceiveProps->shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate。組件更新完成。

順序:
mapstateToprops -> constructor -> componentWillMount -> render -> componentDidMount -> fetchFirstQuarter 該方法是在componentDidMount中調用,是在mapDispatchToProps中綁定到組件上 -> mapstateToprops -> componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate -> componentWillUnmount[切換到其他組件時執行該組件卸載]

二、中間件redux-thunk、redux-saga與mapDispatchToProps使用

1、中間件redux-thunk

  • 中間件redux-thunk

    redux-thunk可以使store的dispatch方法不僅僅是action對象【{type:"",payload:""}】,還可以 接受一個函數並將dispatch傳遞給函數,這樣我們就可以把action這部分代碼提取到單個文件中調用,方便代碼管理。在函數中我們可以進行一些請求api調用的異步操作獲取數據,在進行dispatch到reducer

import { createStore, combineReducers, applyMiddleware } from "redux";
----------------------------thunk中間件-----------------------------------------
import thunkMiddleware from "redux-thunk";
export interface rootState {
  readonly HistoryReducer: HistoryState;
}
//combineReducers 主要針對多reducer處理
const rootReducer = combineReducers<rootState>({ HistoryReducer });
const defaultMiddlewares = [thunkMiddleware];
const store = createStore(rootReducer, applyMiddleware(...defaultMiddlewares));

actions.tsx文件

thunk的action添加異步調用寫法
export const fetchFirstQuarter = () => async dispatch => {
    await axios.get("/IfirstQuarter.json").then(data => dispatch({
      type: FETCH_FIRST_HISTORTY,
        payload: data.data
    }) );
}
// 還可以使用fetch調用api
// 這裏一般會使用async與await處理異步請求
  • 中間件redux-thunk中mapDispatchToProps使用

    mapDispatchToProps爲react-redux的connect函數的第二個參數,用於綁定操作action。其參數類型爲Function | Object => 函數或對象

import { fetchFirstQuarter } from './actions'
mapDispatchToProps寫法1
//  // mapDispatchToProps爲對象類型
//  es6中對象屬性名稱簡寫
// const mapDispatchToProps = { fetchFirstQuarter }
// 原樣
// const mapDispatchToProps = { fetchFirstQuarter:fetchFirstQuarter }
// 解析後爲 對象
// const mapDispatchToProps: {
//   fetchFirstQuarter: () => (dispatch: any) => void;
// }

mapDispatchToProps寫法2
//  // mapDispatchToProps爲函數類型
const mapDispatchToProps = (dispatch:any)=>{
  return ({
     fetchFirstQuarter:()=>{
         // 此處由於使用了react-thunk中間件,因此dispatch參數可以爲一個函數
         dispatch(fetchFirstQuarter());
     	}
     })
}
// 解析後爲 函數
// const mapDispatchToProps: (dispatch: any) => {
//   fetchFirstQuarter: () => void;
// }

export default connect(mapstateToprops, mapDispatchToProps)(FirstQuarter);

在組件中調用action:
this.props.fetchFirstQuarter();

2、中間件redux-saga

在這裏插入圖片描述

  • 中間件redux-saga

    redux-saga中間件是對派發事件action的監聽,與thunk相比多個了一層對store.dispathc的監聽,通過監聽派發事件進而進行攔截事件,在事件中做一些異步請求處理後再繼續請求派發到reducer。使用saga後reducer多了這一層監聽,對比thunk的好處在於使ui組件與業務處理action更加分離,ui組件的部分不會包含處理action請求的東西。在thunk使用時 UI組件一般需要引入action函數在connect的mapDispatchToProps參數與組件進行綁定。而使用saga則不需要,UI組件操作觸發直接dispatch({type:""}),saga通過監聽dispatch,攔截後進行業務處理。

saga的引入
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import rootSaga from './sagas';// saga處理派發請求的根節點文件
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);//這裏注意sagaMiddleware.run 不能放於創建store引用saga中間件之前,否則會出現一個錯誤 Error: Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware

sagas.tsx 文件

單層saga

import { takeEvery,call,put } from 'redux-saga/effects'
import { FETCH_FIRST_HISTORTY } from "./history/constant";
import axios from "axios";

export function* fetchFirstQuarter(dispatch:any) {
    try {
    const result = yield call(axios,"/IfirstQuarter.json"); 
    yield put({
        // 這個type的值對應的是reducer中的action.type,此值不能與觸發fetchFirstQuarter生成器對應的
        // takeEvery("FETCH_FIRST_HISTORTY_ACTION", fetchFirstQuarter)的第一個key參數相同。如		   // 果相同會導致死循環。
        type: FETCH_FIRST_HISTORTY,
        payload: JSON.parse(JSON.stringify(result.data))
      });
    }catch(err){
        console.log(err);
    }
}

export default function* root() {
    // 當容器組件派發的dispatch的type與takeEvery的第一個參數相同時,執行第二個參數對應的生成器函數。
    yield takeEvery("FETCH_FIRST_HISTORTY_ACTION", fetchFirstQuarter); 
  }
  • 容器組件中mapDispatchToProps使用
容器組件:
//const mapDispatchToProps = (dispatch:any)=>{
//  return ({
//     fetchFirstQuarter:()=>{dispatch({type:"FETCH_FIRST_HISTORTY_ACTION"});}
//     })
//}
// 或
const action_fetch = ()=>{
	return{type:"FETCH_FIRST_HISTORTY_ACTION"}
}
const mapDispatchToProps = (dispatch:any)=>{
  return ({
     fetchFirstQuarter:()=>{dispatch(action_fetch());}
     })
}

export default connect(mapstateToprops, mapDispatchToProps)(FirstQuarter);

在組件中調用action:
this.props.fetchFirstQuarter();

​ 在這個組件中,當調用action進行操作時,fetchFirstQuarter函數只進行了dispatch 類型的派發,沒有處理數據或異步請求,真正進行操作的地方在sagas文件中通過生成器函數的taskEvery對該類型的派發監聽。

三、combineReducers與mapStateToProps使用

reducer文件 historyReducers.tsx

const initState = {
  firstQuarter: [] as ReadonlyArray<IfirstQuarter>,
  secondQuarter: [] as ReadonlyArray<IsecondQuarter>
};
export type HistoryState = Readonly<typeof initState>;
export default (state: HistoryState = initState, action): HistoryState => {
  switch (action.type) {
    case FETCH_FIRST_HISTORTY:
      return { ...state, firstQuarter: action.payload };
    case FETCH_SECOND_HISTORTY:      
      return { ...state, secondQuarter: action.payload };
    default:
      return state;
  }
};

1、單個reducer

在項目中的如果reducer文件就只有一個,那我們就可以直接在創建store的時候就進行進行綁定。

單個reducer引入
import { createStore, applyMiddleware } from "redux";
import thunkMiddleware from "redux-thunk";
import HistoryReducer from "./component/history/historyReducers";
const defaultMiddlewares = [thunkMiddleware];
const store = createStore(HistoryReducer, applyMiddleware(...defaultMiddlewares));
------------------------------------------------------------------------------------
在connect的mapStateToProps中的使用
import { HistoryState } from "./historyReducers";

const mapstateToprops = (state:HistoryState) => {
    // 此處對ReadOnly類型的深拷貝
 let firstQuarter:IfirstQuarter[] = JSON.parse(JSON.stringify(state.firstQuarter));
 return ({
     // 此處state獲取的屬性爲reducer中HistoryState類型的屬性
    data: firstQuarter
  })
};
// connect 函數的第一個參數mapstateToprops的類型爲Function,所返回值爲對象,對象的key爲要在組件中映射的屬性,值爲從store中獲取的值
export default connect(mapstateToprops, mapDispatchToProps)(FirstQuarter);

2、多個reducer

在項目中的我們一般不止一個reducer,我們就需要使用redux包提供的combineReducers來管理所有的reducer並統一導出綁定到store。

多個reducer引入
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunkMiddleware from "redux-thunk";
------------------------------------------------------------------------------------
import HistoryReducer, {  HistoryState } from "./component/history/historyReducers";

// 這裏是定義每個reducer的state類型
export interface rootState {
  readonly HistoryReducer: HistoryState;
   // 分號分隔可以寫多個
}
// combineReducers 主要針對多reducer處理
export const rootReducer = combineReducers<rootState>({ 
    HistoryReducer
    // 逗號分隔可以寫多個
});
------------------------------------------------------------------------------------
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
===================================================================================
在connect的mapStateToProps中的使用
import { rootState } from "./historyReducers";

const mapstateToprops = (state:rootState) => {
 // 此處對ReadOnly類型的深拷貝
 let firstQuarter:IfirstQuarter[] = JSON.parse(JSON.stringify(state.HistoryReducer.firstQuarter));
 return ({
     // 此處state獲取的屬性爲combineReducers的泛型類型的HistoryReducer類型的屬性
    data: firstQuarter
  })
};
// connect 函數的第一個參數mapstateToprops的類型爲Function,所返回值爲對象,對象的key爲要在組件中映射的屬性,值爲從store中獲取的值
export default connect(mapstateToprops, mapDispatchToProps)(FirstQuarter);

stQuarter:IfirstQuarter[] = JSON.parse(JSON.stringify(state.HistoryReducer.firstQuarter));
return ({
// 此處state獲取的屬性爲combineReducers的泛型類型的HistoryReducer類型的屬性
data: firstQuarter
})
};
// connect 函數的第一個參數mapstateToprops的類型爲Function,所返回值爲對象,對象的key爲要在組件中映射的屬性,值爲從store中獲取的值
export default connect(mapstateToprops, mapDispatchToProps)(FirstQuarter);








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