yarn add redux react-redux redux-actions redux-logger redux-saga
依赖包说明:
在 store.js 中完成对 redux 仓库的创建:
import { createStore } from 'redux';
const store = createStore();
export default store;
在 src/index.js 文件中引入仓库主文件,并将仓库对象注入到全局:
// ...
import store from './redux/store.js'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
在 /src/redux/reducers 目录中创建一个 categoriesReducers.js 文件,作为商品分类的 reducer 函数配置文件:
import { handleActions } from 'redux-actions'
// 初始值
const initState = {};
const categoriesReducer = handleActions({
// ...
}, initState)
export default categoriesReducer;
将暴露出去的 categoriesReducer 在 combineReducers.js 文件中引入并进行合并:
import { combineReducers } from 'redux'
import categoriesReducers from './reducers/categoriesReducers.js'
export default combineReducers({
categories: categoriesReducers
})
再回到仓库主文件 store.js 中,将合并好的 reducer 添加到仓库中:
import combineReducers from './combineReducers.js'
const store = createStore(combineReducers);
到这一步,商品分类的初始值才成功的保存到仓库中。
在组件中,通过 react-redux 提供的 connect 方法,将组件和仓库关联起来:
import { connect } from 'react-redux'
class Categories extends Component {
// ...
}
const mapStateToProps = state => {
return {}
}
export default connect(mapStateToProps)(Categories)
接下来我们希望能在组件中通过 dispatch 触发 redux 中的异步请求,来获取商品分类数据。因此需要先创建 dispatch 需要的 action 对象。
创建 action 的 type 常量
创建 action 对象需要 type 值,因此我们先在 actionTypes.js 文件中将“发送请求获取分类商品数据”这个操作的 type 值设置出来:
const types = {
GET_CATEGORIES_ASYNC: 'getCategoriesAsync'
}
export default types;
type 常量值创建成功后,就可以在 actions 目录中创建一个 categoriesActions.js 文件,用来创建商品分类相关的 action 对象:
import { createAction } from 'redux-actions'
import types from '../actionTypes'
export const getCategoriesAsyncAction = createAction(types.GET_CATEGORIES_ASYNC);
最后暴露出去的 getCategoriesAsyncAction 是一个函数,该函数内部返回了一个 action 对象。
在商品分类组件中,我们希望组件挂载完成时,就通过 dispatch 方法发出一个命令,让仓库中去发送异步请求获取商品分类数据:
import { getCategoriesAsyncAction } from '../../../redux/actions/categoriesActions.js'
class Categories extends Component {
state = {
data: []
}
componentDidMount() {
this.getCategories();
}
getCategories = async () => {
this.props.dispatch(getCategoriesAsyncAction({ parentId: 0 }))
}
}
组件中发出的命令,需要在 saga 中通过 takeEvery() 方法去侦听。因此,我们在 sagas 目录中,创建一个 categoriesSagas.js 文件,作为商品分类的 saga 处理文件:
import { takeEvery } from 'redux-saga/effects'
import types from '../actionTypes'
function* getCategories() {
}
// 1. 设置侦听函数
function* watchCategories() {
// 第一个参数:侦听的操作对应的 type 值
// 第二个参数:侦听的 type 被 dispatch 时要执行的函数
yield takeEvery(types.GET_CATEGORIES_ASYNC, getCategories)
}
export default watchCategories;
考虑到项目中,不止一个侦听函数,因此,我们在 rootSaga.js 中通过 all() 方法来实现对所有侦听函数的并行启动:
import { all } from 'redux-saga/effects';
import watchCategories from './sagas/categoriesSagas.js'
function* rootSaga() {
yield all([watchCategories()])
}
export default rootSaga;
最后,要让 saga 中间件在 store 中生效,需要在 store.js 中配置:
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
const saga = createSagaMiddleware();
const store = createStore(combineReducers, applyMiddleware(saga));
saga 生效后,还要让 saga 去运行 rootSaga.js 中的函数:
import { createStore, applyMiddleware } from 'redux';
import combineReducers from './combineReducers.js'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './rootSaga.js'
const saga = createSagaMiddleware();
const store = createStore(combineReducers, applyMiddleware(saga));
saga.run(rootSaga);
export default store;
到这一步为止,我们基础配置就已经完成了。
接下来,找到 categoriesSagas.js 文件,在侦听函数执行的 getCategories 方法中,输出 action 对象,来检测组件中 dispatch 时能不能成功触发该方法,同时也查看一下能否接收到组件中传递的 action 对象:
import { takeEvery } from 'redux-saga/effects'
import types from '../actionTypes'
// 2. 组件中 dispatch 时真正要执行的函数
function* getCategories(action) {
console.log(action)
}
// 1. 设置侦听函数
function* watchCategories() {
// 第一个参数:侦听的操作对应的 type 值
// 第二个参数:侦听的 type 被 dispatch 时要执行的函数
yield takeEvery(types.GET_CATEGORIES_ASYNC, getCategories)
}
export default watchCategories;
如果确保该函数能成功执行,也能接收到 action 对象,就下来,就可以在该函数中调用封装好的 api 接口,发送异步请求了:
import { call } form 'redux-saga/effects'
import { getCategoriesAsync } from '../../api/categories'
// 2. 组件中 dispatch 时真正要执行的函数
function* getCategories(action) {
const res = yield call(getCategoriesAsync, action.payload);
console.log(res);
}
如果输出 res 能够接收到后端返回的数据,就说明我们组件通过 redux 发送异步请求成功了。
当我们在 saga 中成功获取到后端返回的仓库数据后,下一步,我们就希望通过 dispatch 发出一个新的命令:用新数据修改仓库中保存的初始旧数据。
因此,针对这个新的操作,我们又需要在 actionTypes.js 中创建新的 type常量值,以及在 categoriesActions.js 中创建对应的 action 对象。
创建 action 的 type 常量
const types = {
GET_CATEGORIES_ASYNC: 'getCategoriesAsync',
SET_CATEGORIES: 'setCategories'
}
export default types;
常量创建完成后,就可以创建 action 对象了:
import { createAction } from 'redux-actions'
import types from '../actionTypes'
// 表示“通过异步请求获取分类数据”
export const getCategoriesAsyncAction = createAction(types.GET_CATEGORIES_ASYNC);
// 表示“修改仓库中的分类数据”
export const setCategoriesAction = (payload) => {
// return 的就是 action 对象
return {
type: types.SET_CATEGORIES,
payload
}
}
action 创建完成后,就可以使用了。
但是,由于在 saga 中获取不到 dispatch,因此,我们可以用 redux-saga 提供的 put 方法来代替 dispatch:
import { getCategoriesAsync } from '../../api/categories'
import { setCategoriesAction } from '../actions/categoriesActions.js'
function* getCategories(action) {
const res = yield call(getCategoriesAsync, action.payload);
if(res.code) {
// 将数据保存到仓库中:希望 dispatch 发出一个命令,调用 reducer 修改仓库数据
yield put(setCategoriesAction(res.data));
}
}
当 put 方法执行时,就会调用 categoriesReducers.js 文件中的 reducer 函数了。
一开始的 reducer 中,我们只保存了分类数据的初始值。
接下来,我们要在 reducer 中配置“修改分类数据”的操作:
import { handleActions } from 'redux-actions'
import types from '../actionTypes.js'
const initState = {};
const categoriesReducer = handleActions({
[types.SET_CATEGORIES]: (state, action) => {
// console.log('接收到的是 saga 中通过 put 传递的值', action);
return action.payload;
}
}, initState)
export default categoriesReducer;
配置完成后,当 saga 中调用 put 方法时,就会执行 reducer 函数中对应的 type 的操作了。
我们就能成功的将后端返回的数据保存到仓库中了。
我们在前面第五步时,就用组件去关联了仓库。
现在,只需要在 mapStateToProps 方法中将仓库的分类数据传递给组件的 props 即可:
const mapStateToProps = state => {
console.log('组件中获取仓库中所有的数据', state);
return {
data: state.categories.data
}
}
这样,组件中就可以通过 this.props.data 来访问仓库中的分类数据了。