用Redux做数据隔离

本文探讨了在React应用中如何利用Redux进行数据隔离,通过Redux-Toolkit和Redux-thunk处理异步请求。介绍了如何在不编写额外模板代码的情况下,优化数据流和业务逻辑,提供更佳的开发体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【背景】

在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中处理,各家有各家的说法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值