先简单汇总一些本部分的内容:

redux 高级 api 作用描述
applyMiddleware() 使用中间件
combineReducers() 合并 reducer

# 一、redux 增强

# 1、需求分析

  常规情况下,我们是如何在 redux 中发送 ajax 请求的呢?

redux 与 网络请求

  在 componentDidMount(进入组件时)中发生 ajax 请求,并通过 dispatch 将获取的 res.data 保存到 redux 中的 state.dataList 中。

class Demo extends PureComponent {
  //
  componentDidMount() {
    axios.get('/data.json').then((res) => {
      this.props.getArticleList(res.data)
    })
  }
}

const mapDispatchToProps = (dispatch) => ({
  getArticleList: function (data) {
    dispatch(getArticleListAction(data))
  }
})

export default connect(null, mapDispatchToProps)(Demo)
import { UPDATE_ARTICLE_LIST } from './constants.js'

// 1、常规操作(返回对象)
export const getArticleListAction = (name) => ({
  type: UPDATE_ARTICLE_LIST,
  data
})
// reducer.js

const initialState = {
  articleList: []
}

function reducer(state = initialState, action) {
  switch (action.type) {
    case UPDATE_ARTICLE_LIST:
      return { ...state, articleList: action.data }
    default:
      return state
  }
}

export default reducer

  但这显然不利于网络请求管理,那我们可不可以在 redux 中实现像 vuex 那样在 action 中统一处理异步请求的功能呢?

思路

  我们可以将 ajax 请求部分的代码直接放到 redux 的 actionCreators.js 中管理,在需要的时候,通过 dispatch 获取触发请求即可。

# 2、redux-thunk 库

  上述方案看似可行,但是 redux 中仅支持 dispatch({ ... })。但是对象中是不能进行 异步请求的。需要达到该目的,我们得想办法让 redux 支持 store.dispatch(function)

  解决方案之一就是使用 redux-thunk 库

安装:

npm install redux-thunk

使用:

redux-thunk 实现原理
// @/store/index.js

function thunk(store) {
  const next = store.dispatch

  function dispatchThunk(action) {
    if (typeof action === 'function') {
      action(store.dispatch, store.getState)
    }
    if (typeof action === 'object') {
      next(action)
    }
  }

  // Monkey Patching技术
  store.dispatch = dispatchThunk
}

// thunk(store)
applyMiddleware 实现原理
// @/store/index.js

function applyMiddleware(...fns) {
  fns.forEach((fn) => {
    fn(store)
  })
}

// export default applyMiddleware
// @/store/index.js

import { createStore, applyMiddleware } from 'redux'
import thunk from 'react-thunk'

import reducer from './reducer.js'

// export default createStore(reducer)
export default createStore(reducer, applyMiddleware(thunk)) // 中间件增强

# 3、网络请求 ✨

  在 actionCreators.js 保存着待 dispatch 的内容,其中 getArticleListAction 是一个函数(以前 xxxAction 大多是对象)。

dispatch(getArticleListAction)

  • 编写 actionCreators.js
import { NAME_CHANGE, UPDATE_ARTICLE_LIST } from './constants.js'

// 1、常规操作(返回对象)
export const nameChangeAction = (name) => ({
  type: NAME_CHANGE,
  name
})

// 2、异步操作(返回函数)(redux-thunk支持)
export const getArticleListAction = () => {
  return function (dispatch, getState) {
    axios.get('/data.json').then((res) => {
      dispatch({ type: UPDATE_ARTICLE_LIST, data: res.data }) // 反正在actionCreators中,就不用封装了
    })
  }
}
  • 使用示例
class Demo extends PureComponent {
  //
  componentDidMount() {
    // axios.get('/data.json').then((res) => {
    //   this.props.getArticleList(res.data)
    // })
    this.props.getArticleList()
  }
}

const mapDispatchToProps = (dispatch) => ({
  getArticleList: function () {
    dispatch(getArticleListAction())
  }
})

export default connect(null, mapDispatchToProps)(Demo)

# 拓展:redux-devtools

  我们只需要将我们使用过的中间件用 redux-devtools 提供的 composeEnhancers包裹一下即可:

redux-devtools (opens new window)

// @/store/index.js

import { createStore, applyMiddleware, compose } from 'redux'
// import { createStore, applyMiddleware } from 'redux'

import thunk from 'react-thunk'
import reducer from './reducer.js'

// 开启✨redux-devtools
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

export default createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
// export default createStore(reducer, applyMiddleware(thunk)) // 中间件增强

# 二、模块化开发

# 1、需求分析

  当目前为止,我们已经完成了:

store 的拆分
├── store
    ├── index.js             # store
    ├── constants.js         # 保证action命名一致
    ├── reducer.js           # reducer逻辑抽离
    └── actionCreators.js    # action使用封装
react-redux 的应用
// App.jsx

// ……

// 1、state映射
const mapStateToProps = (state) => ({
  count: state.counter
})

// 2、dispatch映射
const mapDispatchToProps = (dispatch) => ({
  countChange: function (str) {
    dispatch(countChangeAction(str))
  }
})

// connect() 返回的是一个高阶组件
export default connect(mapStateToProps, mapDispatchToProps)(App)
网络请求的统一管理
// actionCreators.js

import { NAME_CHANGE, UPDATE_ARTICLE_LIST } from './constants.js'

export const getArticleListAction = () => {
  return function (dispatch, getState) {
    axios.get('/data.json').then((res) => {
      dispatch({ type: UPDATE_ARTICLE_LIST, data: res.data }) // 反正在actionCreators中,就不用封装了
    })
  }
}

  这些东西完全足够我们在常规项目中使用 redux 了。

  但是,如果是大型项目的话,随着项目的进行,单个 store 中的各大文件会变得越来越大,那该怎么解决呢?

# 2、模块拆分

  Redux 和 Pinia 一样,是可以创建多个 store 的,但是在《邂逅 Redux》中我们就提到了其三大原则之一:单一数据源。所以,Redux 建议的是只创建一个单一的 store。

  如果使用单个 store 的话,我们可以采用类似于 vuex 的方式,将核心部分根据需要分散开来,然后有 index.js 统一汇总导出。

vuex 的模块化
├── store
    ├── index.js
    ├── getters.js        # 根级别的 getters
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js
        └── products.js
├── store
    ├── index.js
    ├── cart
    │   ├── index.js
    │   ├── constants.js
    │   ├── reducer.js
    │   └── actionCreators.js
    └── products
        ├── index.js
        ├── constants.js
        ├── reducer.js
        └── actionCreators.js

# 3、combineReducers ✨

  通过上面的描述我们大概知道了,Redux 中的模块化是需要根据需求将 reducer.js 进行分散。

  关于如何将分散的 reducer.js 统一起来,我们可以使用 redux 提供的 combineReducers

combineReducers (opens new window) 可以合并多个 reducer

combineReducers 实现原理
function reducer(state = {}, action) {
  return {
    cart: cartReducer(state.cart, action),
    products: productsReducer(state.products, action)
  }
}
  • redux 的根 index.js
// import { createStore, applyMiddleware } from 'redux'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunk from 'react-thunk'

// import reducer from './reducer.js'
import cartReducer from './cart'
import productsReducer from './products'
const reducer = combineReducers({
  cart: cartReducer,
  products: productsReducer
})

export default createStore(reducer, applyMiddleware(thunk)) // 中间件增强
  • 各模块中的 index.js
// 模块中的index.js ---> 进行统一导出

export * from './reducer.js'
export * from './actionCreators.js'

# 4、注意事项

  模块化划分后,我们的 dispatch 还是像以前一样正常使用(因为真正触发 dispatch 操作是在 actionCreators.js 中)。

  但是使用 state 时,就可以些许区别了(state 是受模块化划分影响的):

TIP

  到这里的话,redux 可以真正的告一段落了,最后,强行重写一些counter 案例(模块化版) 😂

this.state = {
  // count: store.getState().counter
  count: store.getState().cart.counter
}

或者:

const mapStateToProps = (state) => ({
  // count: state.counter
  count: state.cart.counter
})
更新于 : 7/8/2024, 10:21:14 AM