React--Redux③(异步数据流和模块拆分)

异步数据流(redux-thunk)

  • 网络请求可以在类组件的 componentDidMount 中发送,所以可以有这样的结构:
javascript 复制代码
 componentDidMount() {
   axios.get(url).then(res => {
     const banners = res.data.banner.list
     this.props.changeBanners(banners)
   })
 }
  • 不合理的点: 异步请求的数据是需要放到 Store 中的,也就是和 Redux 相关,那么异步请求更改数据应该是属于 Redux 的一部分

将异步请求交给 Redux 来管理

  • dispatch 是个同步操作,并且只能支持传入一个普通对象,如果将 actionCreator 改成 async 函数,那么他会返回一个 Promise
javascript 复制代码
 // 错误操作,这里action生成器返回的不是普通对象
 const changeBannersAction = async () => {
   const { data:{ data } } = await axios.get(url)
   const banners = data.banner.list
   return { type: actionTypes.CHANGE_BANNERS, banners }
 }
  • 如果使用 then 链式操作,则拿不到服务器返回的数据
javascript 复制代码
 const changeBannersAction = () => {
   axios.get(url).then(res => {
      const banners = res.data.data.banner.list
   })
   // 这里拿到不banners
   return { type: actionTypes.CHANGE_BANNERS, banners }
 }
  • 这时需要使用官方推荐的库 redux-thunkstore 增强,让 dispatch 可以接收一个函数
  • 安装 redux-thunk
shell 复制代码
 npm i redux-thunk
  • 使用 redux-thunk 和中间件增强 store
javascript 复制代码
 import { createStore, applyMiddleware } from "redux";
 import thunk from 'redux-thunk'
 import reducer from './reducer'
 ​
 const store = createStore(reducer, applyMiddleware(thunk))
 ​
 export default store
  • 这时 dispatch 就能接收函数,并且该函数立即调用,action 生成器返回函数

返回的函数携带两个参数

  • dispatch 函数,用于分发 action
  • getState 函数,用于获取 state 中的数据
javascript 复制代码
 const changeBannersAction = () => async (dispatch, getState) => {
   const { data:{ data } } = await axios.get(url)
   const banners = data.banner.list
   dispatch({type: actionTypes.CHANGE_BANNERS, banners})
 }
  • 在组件中映射并调用
javascript 复制代码
 class Category extends PureComponent {
   componentDidMount() {
     this.props.changeBanners()
   }
 }
 ​
 const mapActionToProps = (dispatch) => ({
   changeBanners(){
     dispatch(changeBannersAction())
   }
 })
 ​
 const mapStateToProps = (dispatch) => ({
   bannners: state.banners
 })
 ​
 export default connect(mapStateToProps, mapActionToProps)(Category)

redux-thunk 优点

  • 体积小: redux-thunk 的实现方式很简单,只有不到 20 行代码
  • 使用简单: redux-thunk 没有引入像 redux-sagaredux-observable 额外的范式,上手简单

redux-thunk 缺陷

  • 样板代码过多:Redux 本身一样,通常一个请求需要大量的代码,而且很多都是重复性质的
  • 耦合严重: 异步操作与 action 耦合在一起,不方便管理
  • 功能孱弱: 有一些实际开发中常用的功能需要自己进行封装

中间件原理

  • 中间件底层是对原来的处理做了拦截,利用一个 hack 技术 Monkey Patching,修改原有的程序逻辑
  • 例如 dispatch 原本只可以传入一个 action 对象,使用 thunk 后可以传入函数,其实就是底层拦截了storedispatch 的调用过程
javascript 复制代码
 const patchingThunk = (store) => {
  const next = store.dispatch;
 ​
  const patchingDispatch = (action) => {
   console.log(action); // 在原来的dispatch之前做其他处理
   next(action)
  };
 ​
  store.dispatch = patchingDispatch;
 }
 ​
 patchingThunk(store);

Thunk核心逻辑

  • 在调用 dispatch 的过程中,真正调用的函数其实是 patchingDispatch,那么当 dispatch 传入函数时就可以做特殊处理
javascript 复制代码
 const patchingThunk = (store) => {
  const next = store.dispatch;
 ​
  const patchingDispatch = (action) => {
   if(Object.prototype.toString.call(action) === '[object Function]'){
   // 这里的dispacth是patchingDispatch,若再次传入函数也可处理
   action(store.dispatch, store.getState)
   }else if(Object.prototype.toString.call(action) === '[object Object]' && action.type){
     next(action)
   }else {
     // 其他情况则抛出错误
   }
   next(action)
  };
   
  store.dispatch = patchingDispatch;
 }

模块拆分

  • 如果将所有更新逻辑都放入到单个 reducer 函数中,项目将变得难以维护,因此,把 reducer 拆分成一个个小模块是很好的编程方式
  • 每个模块的 reducer 仅负责管理该模块所使用的数据

基本拆分

  • reducer 更新逻辑拆分
javascript 复制代码
 // store/category/reducer.js
 import * as actionTypes from './constant';
 ​
 const initialState = {
   banners: [],
   recommends: []
 }
 ​
 function reducer(state = initialState, action) {
   switch(action.type){
     case actionTypes.CHANGE_BANNERS:
       return { ...state, banners: action.banners };
     case actionTypes.CHANGE_RECOMMENDS:
         return { ...state, recommends: action.recommends };
     default:
       return state
   }
 }
 ​
 export default reducer
  • actionType 常量拆分
javascript 复制代码
 // store/category/constant.js
 const CHANGE_BANNERS = 'change_banners';
 const CHANGE_RECOMMENDS= 'change_recommends';
 ​
 export { 
   CHANGE_BANNERS,
   CHANGE_RECOMMENDS 
 }
  • actionCreators 拆分
javascript 复制代码
 // store/category/actionCreators.js
 import * as actionTypes from './constant';
 import axios from 'axios'; // 举例使用,项目中需要封装
 ​
 const fetchHomeMultidataAction = () => async (dispatch) => {
   const { data:{ data } } = await axios.get(url)
   const banners = data.banner.list
   const recommends = data.recommend.list
   dispatch({ type: actionTypes.CHANGE_BANNERS, banners })
   dispatch({ type: actionTypes.CHANGE_RECOMMENDS, recommends })
 }
 ​
 export { 
   fetchHomeMultidataAction
 }
  • index 文件负责暴露内容
javascript 复制代码
 import reducer from './reducer';
 ​
 export default reducer; // 暴露自己的reducer
 export * from './actionCreators'; // 暴露action生成器

合并reducer

  • 当将 reducer 拆分成为多个模块后,拆分之后的 reducer 都是相同的结构,并且每个 reducer 独立负责管理自身 state 的更新
  • 那么需要用 Redux 提供的 combineReducers 函数,将各模块的 reducer 组合成一个 Reducer
  • combineReducers 作用: 把多个不同 reducer 作为 value 的对象,合并成一个最终的 reducer
  • combineReducers 函数接收一个拆分后的 reducer 函数组成的对象
javascript 复制代码
 import { createStore, combineReducers } from "redux";
 import counterReducer from './counter';
 import categoryReducer from './category';
 ​
 const reducer = combineReducers({
   counter: counterReducer,
   category: categoryReducer
 })
 ​
 const store = createStore(reducer)
 ​
 export default store
  • 合并后的 reducer 可调用各模块的 reducer,并将它们的返回结果合并成一个 state 对象
  • combineReducers 返回的 state,会将传入的每个 reducer 返回的 state 按对应的 key 命名
  • 获取 state 数据时,需要按模块名称区分
javascript 复制代码
 const mapStateToProps = (state) => ({
   count: state.counter.count, // 获取counter模块的state数据
   banners: state.category.banners, // 获取category模块的state数据
   recommends: state.category.recommends
 })
  • 使用 action 需要在对应的模块下引入
javascript 复制代码
 import { mulCountAction } from '../store/counter';
 import { fetchHomeMultidataAction } from '../store/category'
相关推荐
余生H18 分钟前
深入理解HTML页面加载解析和渲染过程(一)
前端·html·渲染
吴敬悦1 小时前
领导:按规范提交代码conventionalcommit
前端·程序员·前端工程化
ganlanA1 小时前
uniapp+vue 前端防多次点击表单,防误触多次请求方法。
前端·vue.js·uni-app
卓大胖_1 小时前
Next.js 新手容易犯的错误 _ 性能优化与安全实践(6)
前端·javascript·安全
m0_748246351 小时前
Spring Web MVC:功能端点(Functional Endpoints)
前端·spring·mvc
SomeB1oody1 小时前
【Rust自学】6.4. 简单的控制流-if let
开发语言·前端·rust
云只上1 小时前
前端项目 node_modules依赖报错解决记录
前端·npm·node.js
程序员_三木1 小时前
在 Vue3 项目中安装和配置 Three.js
前端·javascript·vue.js·webgl·three.js
lxw18449125141 小时前
vue 基础学习
前端·vue.js·学习
徐_三岁1 小时前
Vue3 Suspense:处理异步渲染过程
前端·javascript·vue.js