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








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