先简单汇总一些本部分的内容:
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
包裹一下即可:
// @/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
})