【背景】
在react组件中我们常常要写很多请求的模板代码,即使在拿到数据之后不做任何业务逻辑。
import getListData from '...'
this.setState({loading:true})
getListData(params).then(res => {
if(res && res.retcode == 0){
let data = res.result_rows.list || []
this.setState(...)
this.setState({loading:true})
// ..没有业务逻辑
}
})
当我们需要处理数据或编写业务逻辑时,代码就更多了,这开发体验确实不太好。
【Redux处理】
在普通的redux使用中,数据流走向是这样的。
本需求中需要加入中间件来处理http请求,有了中间件之后,数据流变成了这样。
也就是说,在提交了action之后,先经过一个个中间件处理,再把处理后的action交给reducer。这样一来思路就清晰了 ~
结合代码来看吧
action
- type为一个数组,包含三种状态(加载中、请求成功、请求失败)
- 这里的type、promise、payload(请求参数)都会先传递给中间件处理
// import {GET_AUTH, GET_AUTH_LOAD, GET_AUTH_ERROR} from './action-types';
export const GET_AUTH = 'GET_AUTH'
export const GET_AUTH_LOAD = 'GET_AUTH_LOAD'
export const GET_AUTH_ERROR = 'GET_AUTH_ERROR'
import {getAuth} from '../../request/api'
let actions = {
getAuth: function(payload:any) {
return {
type: [GET_AUTH_LOAD,GET_AUTH,GET_AUTH_ERROR],
promise: getAuth(payload),
payload
};
}
};
export default actions;//导出ActionCreators
promiseMiddware
- 进来首先判断action传过来的参数是否有promise,如果没有,说明不是一个http请求的dispatch,直接传递给下一个中间件(如果后面没有中间件了,就是直接传递给reducer)
- 首先把LOADING状态的type传给对应的reducer
- 处理promise请求,根据响应选择传递给SUCCESS/ERROR对应的Reducer
/**
* 中间件处理请求
*/
const promiseMiddleware = () => next => (action) => {
if (!action.promise) {
return next({...action});
}
const { type , promise } = action;
/*
* 含有promise的 action 的 type必须规范为[XX_LOAD, XX , XX_ERROR]
*/
const [LOADING, SUCCESS, ERROR] = type;
// 首先我们让LOADING type 先回到对应的action,因为请求还没完,所以没有return
next({ ...action, type: LOADING });
return promise.then((res) => {
// 请求成功
if (res.retcode == 0) {
// 走到SUCCESS type
return next({ ...action, type: SUCCESS, result: res });
} else {
// 走到ERROR type
return next({ ...action, type: ERROR, result: res });
}
}).catch(err => { // 异常情况
next({ ...action, type: ERROR });
});
};
export default promiseMiddleware;
reducer
- action经过中间件的处理,走到reducer中,authReducer根据不同的type处理业务逻辑,并返回一个新的state给视图。这样一个数据流就走完了。
// import {GET_AUTH, GET_AUTH_LOAD, GET_AUTH_ERROR} from '../action/action-types';
export const GET_AUTH = 'GET_AUTH'
export const GET_AUTH_LOAD = 'GET_AUTH_LOAD'
export const GET_AUTH_ERROR = 'GET_AUTH_ERROR'
//定义默认状态
let initState = {};
// 处理reducer
const authReducer = {
// 加载
[GET_AUTH_LOAD](state = initState,action){
return {...state, loading:true}
},
// 获取信息成功
[GET_AUTH](state = initState,action){
let info = action.result.result_rows || {}
return {...state, loading:false, ...info}
},
// 获取失败
[GET_AUTH_ERROR](state = initState,action){
return {...state, loading:false}
}
}
// action 经过中间件走到这 根据不同的type到authReducer中找到处理方法 并返回新的state
function reducer(state=initState,action:Action){
const fn = authReducer[action.type] || null
// 如果找不到type对应的处理,返回当前的state
return fn ? fn(state, action) : state
}
export default reducer;
视图文件
import React from 'react'
import titleAction from '@store/action/title'
import authAction from '@store/action/auth'
import {connect} from 'react-redux'
class Index extends React.Component<any,any>{
constructor(props:any){
super(props)
}
componentDidMount(){
// redux中处理直接把数据塞到props中了
this.props.getAuth({})
}
render(){
const {name,loading} = this.props.auth
return (
<div style={style}>
<h1>{loading ? '加载中':'加载完毕'}</h1>
<h1>欢迎{name}</h1>
</div>
)
}
}
// 把state变成props引入该组件
function mapStateToProps(state:any) {
return {
auth:{
...state.authReducer
}
}
}
const mapDispatchToProps = (dispatch:any) => {
return {
getAuth: (payload:Object) => dispatch(authAction.getAuth(payload))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Index)
Redux-Toolkit
Redux-Toolkit 是官方推出的一个工具库,相较于老一套的hook + redux + redux-thunk解决方案要更为简便。比如不用写Actions, ActionTypes了,内嵌了Redux-thunk从而对async有了更好的支持。搭配useDispatch、useSelector使用非常香。
下面用toolkit重写上面获取Auth信息的例子
首先要重写一下redux的导出文件 store/index.ts
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import {createStore,combineReducers,applyMiddleware} from 'redux';
import titleReducer from './reducers/title';
import authReducer from './reducers/auth';
import authToolkitReducer from './reducers/auth-toolkit'
import middlewares from '../middleware/index'
const preloadedState = {}
const rootReducer = combineReducers({
authReducer,
titleReducer,
authToolkitReducer
})
// 老写法
// const store = createStore(
// rootReducer,
// preloadedState,
// applyMiddleware(...middlewares)
// );
// 新写法
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware().concat(middlewares),
devTools: process.env.NODE_ENV !== 'production',
preloadedState,
});
reducer/auth-toolkit.ts
// 用toolkit 重写 redux
// getAuthInfo => middleware => reducer => views
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import {getAuth} from '../../request/api'
// 相当于以前的action
export const getAuthInfo = createAsyncThunk(
'auth/getInfo', // type
async (params:any)=>{
const res = await getAuth(params || {})
return res; // 给到中间件
}
)
const authToolkitSlice = createSlice({
name: 'auth-toolkits',
initialState:{
loading: false
},
reducers:{},
extraReducers:{
// action 为 经过中间件处理后穿过来的
// 数据在payload中
// 请求完成
[getAuthInfo.fulfilled]:(state:any,action)=>{
let result_rows = action.payload.result_rows
state.name = result_rows.name
state.loading = false
},
// 请求中
[getAuthInfo.pending]:(state:any,action)=>{
state.loading = true
},
// 请求失败
[getAuthInfo.rejected]:(state:any,action)=>{
state.loading = false
},
}
})
export default authToolkitSlice.reducer
视图文件
import React,{useState,useEffect,useContext} from 'react'
import {getAuthInfo} from '@store/reducers/auth-toolkit'
import { useDispatch, useSelector } from 'react-redux';
const Index = (props:any) =>{
const dispatch = useDispatch()
const { name,loading } = useSelector(
(state) => {
// 获取响应模块的state
return state.authToolkitReducer
}
);
useEffect(()=>{
// 触发请求
dispatch(getAuthInfo({}))
},[])
return (
<div className="container">
<h1>
{loading ? '加载中': `你好, ${name}`}
</h1>
</div>
)
}
export default Index
redux-toolkit 写同步
下面是一个同步redux的例子
import { createAction, createReducer, nanoid } from '@reduxjs/toolkit'
export const setHeaderShow = createAction('global/setHeader', function prepare(show: boolean) {
return {
payload: {
show: show
}
}
})
const initialState = {
isShowHeader: true
}
const globalReducer = createReducer(initialState, (builder) => {
builder
.addCase(setHeaderShow, (state, action) => {
state.isShowHeader = action.payload.show
})
})
export default globalReducer
视图中调用:
import { setHeaderShow } from '@store/reducers/global'
import { useDispatch, useSelector } from 'react-redux'
const dispatch = useDispatch()
dispatch(setHeaderShow(true)
const { isShowHeader } = useSelector(
(state:any) => {
return state.global // global是reducer的名字
}
)
当然,同步方法也可以简化到切片slice中
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
//异步方法
const fetchBusinessSettleDetail = createAsyncThunk(
'settle/fetchBusinessSettleDetail', // action type
async () => {
const res = await getBusinessSettleDetail({ business_id });
return res.result_rows;
}
);
// 创建切片
const settleSlice = createSlice({
name: 'settle',
initialState: {
// ...
},
reducers: {
// 同步方法
setHeaderShow: (state,action) => { state.isShowHeader = action.payload }
},
extraReducers: {
[fetchBusinessSettleDetail.fulfilled]: (state, action) => {
state.xx = action.xx
},
[fetchBusinessSettleDetail.rejected]: (state, action) => {
....
},[fetchBusinessSettleDetail.pending]: (state, action) => {
....
},
}
}
// 同步方法
const {setHeaderShow} = globalSlice.actions
export{
setHeaderShow,
fetchBusinessSettleDetail
}
export default settleSlice.reducer;
处理异常
// 相当于以前的action
export const getAuthInfo = createAsyncThunk(
'auth/getInfo', // type
async (params:any, { rejectWithValue })=>{
const res = await getAuth(params || {})
if(res.retcode != 0){
return rejectWithValue('')
}
return res; // 给到中间件
}
)
[getAuthInfo.rejected]: (state, action) => {
// 会走到这里来
},
Redux-thunk
redux-thunk是比较经典的redux异步处理方案
action
action中做异步操作
import {
GET_OPTIONS_SUC,
GET_OPTIONS_LOAD,
GET_OPTIONS_ERROR,
} from "../constants/insure";
import { queryConfig } from "../request/api/global";
export const getOptions = (actions = {}) => {
return dispatch => {
dispatch({
type: GET_OPTIONS_LOAD
})
queryConfig({ data: actions }).then(res => {
const { result, res_info, result_rows = {} } = res as any;
if (result !== 0) {
dispatch({
type: GET_OPTIONS_ERROR
});
} else {
dispatch({
type: GET_OPTIONS_SUC,
payload: {
...result_rows
}
});
}
});
};
};
reducer
在action中获取请求数据,传递给reducer处理并更新state
const INITIAL_STATE = {
options: {
cert_type: {},
user_relation: {},
channel_type: {},
order_status: {},
order_user_type: {},
pay_status: {},
policy_status: {},
wx_trade_type: {},
yibao_type: { "0": "有医保", "1": "没医保" },
sale_type: { "0": "轻享版", "1": "奢华版" }
}
};
/**
* @param state
* @param action
*/
export default function insure(state = INITIAL_STATE, action) {
switch (action.type) {
// 查询配置信息
case GET_OPTIONS_SUC:
const options = action.payload || {};
const newState = {
...state,
options:{
...state.options,
...options
}
}
return newState
default:
return state;
}
}
组件内使用
import { getOptions } from "./actions/insure"; // 引入action
const dispatch = useDispatch();
dispatch(getOptions());
const { options } = useSelector(state => {
return state.insure;
});
概念总结:
1.actioncreator : actioncreator的设计也是由Flux架构来的产物,它是一种辅助用的函数,用来创建Action的, 上面代码中的 getOptions就是一个creator。可以看到在请求数据的三个时期(pending,fulfilled,reject),creator都分别抛出了一个action(传递给reducer)
2.对于数据逻辑要在actioncreator中处理还是在reducer中处理,各家有各家的说法。