1. Redux 核心

目标:搞懂 Redux 本身是怎么回事;以及 Redux 如何使用

1.1 什么是 Redux

  1. Redux 是一个状态容器【即保存了一些数据的 JavaScript 对象,这些数据跟页面中 DOM 元素的状态是对应关系】

image.png

状态容器的好处:不需要查找页面的 DOM 元素去操作它,可以直接操作 DOM 元素对应的状态对象就好了

问:仅仅操作 DOM 元素对应的状态对象,是如何将状态更新到页面中的?
答:DOM 的操作可以交给框架去操作

  1. Redux 提供可预测化的状态管理

Redux 提供了一种科学的状态管理方式,通过这种方式,当状态发生变化时,该就变得可预测。当状态出现问题的时候,我们很容易定位到出问题的地方。

1.2 Redux 核心概念以及 Redux 工作流程

Redux 中有 4 个核心概念:

  • Store:存储状态的容器,是一个 JavaScript 对象。Redux 要求我们把所有的状态都存储到 Store 中
  • View:视图,HTML 页面
  • Actions:Javascript对象,对象中要有一个固定的属性 type,type是一个字符串。它的作用是用来描述要对 Store 状态进行什么样的操作
  • Reducer:函数,用来向 Store 中存储状态,以及更新 Store 中的状态。也就是说 Reducer 中返回什么,store 就存什么

Redux 工作流程
Redux 的 4 个核心概念组合起来,要怎么使用?

  • 视图(View) 是不能直接操作 Store 状态的,只能先通过触发(dispatch) Actions 描述当前需要对 Store 进行怎样的操作
  • 而 Actions 会被 Reducer 接收到,在其内部会根据 Actions 的 type 的属性值进行相应操作,当状态处理完成之后返回最新处理好的状态,然后更新到 Store 中。
  • Store 中的状态更新完成之后,同步(subscribe)给视图

image.png

1.3 Redux 核心 API

  • createStore:用于创建 store 状态的方法,它的第一个参数是 Reducer,第二个参数是 Reducer 的默认参数(默认 state 状态对象)
    • Reducer:是一个函数,它接受两个参数,一个是 state,一个是 action
  • getState():获取 store 中存储的状态
  • subscribe():订阅状态
  • dispatch():触发 action

1.4 Redux 计数器

  1. // 3. 默认状态
  2. var initialState = { count: 0 }
  3. // 2. 创建 Reducer 函数,该函数其实就是 createStore 的第一个参数
  4. var reducer = function (state = initialState, action) {
  5. switch (action.type) {
  6. case 'increment':
  7. return { count: state.count + 1 }
  8. case 'decrement':
  9. return { count: state.count - 1 }
  10. default: state
  11. }
  12. }
  13. // 1. 创建 Store 对象
  14. var store = Redux.createStore(reducer)
  15. // 4. 定义 action
  16. var increment = { type: 'increment' }
  17. var decrement = { type: 'decrement' }
  18. // 5. 获取按钮,添加点击事件
  19. document.getElementById('plus').onclick = function () {
  20. // 6. 触发 action
  21. store.dispatch(increment)
  22. }
  23. document.getElementById('minus').onclick = function () {
  24. // 6. 触发 action
  25. store.dispatch(decrement)
  26. }
  27. // 7. 订阅 store
  28. store.subscribe(() => {
  29. // 8. 更新视图
  30. document.getElementById('count').innerText = store.getState().count
  31. })

2. React + Redux

目标:React 中如何使用 Redux;解决 React 中组件与组件之间共享数据的问题

2.1 React 中不使用 Redux 时的问题

  • React 组件之间通信的数据流是单向的,上层组件可以通过 props 属性向下层组件传递数据,而下层属性不能向上层组件传递数据。
  • 如果需要更改数据,下层组件只能通过调用上层组件传递的修改数据的方法。
  • 当项目越来越大的时候,组件之间传递数据、传递修改数据的方法就会变得越来越困难

2.2 React 中使用 Redux 的好处

  • Redux 应用程序要求我们要把所有的状态都存储到 store 状态容器当中,
  • 由于 Store 是独立于组件之外的,使得数据也独立于组件之外,如果有哪个组件需要使用到该数据的,可以直接从 store 中去获取即可。
  • 从而解决了组件与组件之间数据传递困难的问题

2.3 React 中使用 Redux

Redux 真正工作流程:
image.png

  1. 组件通过dispatch方法触发Action
  2. Store接收Action并将Action分发给Reducer
  3. Reducer根据Action类型对状态进行更改并将更改后的状态返回给Store
  4. 组件订阅了Store 中的状态,Store中的状态更新会同步到组件。

2.4 React + redux 的使用

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { createStore } from 'redux';
  4. const initialState = { count: 0 }
  5. function reducer(state = initialState, action) {
  6. switch (action.type) {
  7. case 'increment':
  8. return { count: state.count + 1 }
  9. case 'decrement':
  10. return { count: state.count - 1 }
  11. default: return state
  12. }
  13. }
  14. const store = createStore(reducer)
  15. const increment = { type: 'increment' }
  16. const decrement = { type: 'decrement' }
  17. function Counter() {
  18. return <div>
  19. <button onClick={() => store.dispatch(decrement)}>-</button>
  20. <span>{store.getState().count}</span>
  21. <button onClick={() => store.dispatch(increment)}>+</button>
  22. </div>
  23. }
  24. store.subscribe(() => {
  25. ReactDOM.render(
  26. <React.StrictMode>
  27. <Counter />
  28. </React.StrictMode>,
  29. document.getElementById('root')
  30. );
  31. })
  32. ReactDOM.render(
  33. <React.StrictMode>
  34. <Counter />
  35. </React.StrictMode>,
  36. document.getElementById('root')
  37. );

至此 react + redux 的基础代码已经实现了,但是存在一些问题:

  • 代码写的太生硬了,就比如 render 写了两遍;
  • 而且组件应该分离到单独组件中去的。但是如果分离到单独组件又拿不到 store 了。

怎么解决?如何将 React 和 redux 完美结合使用?

2.5 React + redux + react-redux

分离时遇到的问题和解决方案

  1. 当我们尝试将Counter抽离出去放在Component文件夹时,发现我们在这里就无法拿到Store了。——把Store变为全局可取的

下载 Redux:npm i redux react-redux

  • react-redux
    • Provider:组件,它可以把 store 放到一个全局的地方。Provider 组件要包裹项目中所有的组件,也就是要作为最外层的组件使用
    • connect:方法,
      • 内部会订阅 store,当 store 中的状态变化时会重新渲染组件
      • 通过 connect 方法拿到 store 的状态,将状态映射到组件的 Props 属性中:connect 方法的第一个参数
      • 通过 connect 方法拿到 dispatch 方法:connect 方法的第二个参数

Provider 的使用

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { Provider } from 'react-redux';
  4. import { createStore } from 'redux';
  5. import Counter from './components/counter';
  6. const initialState = { count: 0 }
  7. function reducer(state = initialState, action) {
  8. switch (action.type) {
  9. case 'increment':
  10. return { count: state.count + 1 }
  11. case 'decrement':
  12. return { count: state.count - 1 }
  13. default: return state
  14. }
  15. }
  16. const store = createStore(reducer)
  17. const increment = { type: 'increment' }
  18. const decrement = { type: 'decrement' }
  19. ReactDOM.render(
  20. <Provider store={store}>
  21. <Counter />
  22. </Provider>,
  23. document.getElementById('root')
  24. );

connect 方法的使用

  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. function Counter(props) {
  4. return <div>
  5. <button onClick={() => props.dispatch({ type: 'decrement' })}>-</button>
  6. <span>{props.count}</span>
  7. <button onClick={() => props.dispatch({ type: 'increment' })}>+</button>
  8. </div>
  9. }
  10. const mapStateToProps = (state) => ({
  11. count: state.count
  12. })
  13. // connect 方法接收一个函数,这个函数的参数是 store 中的 state,返回一个对象,这个对象会被映射到组件的 props 属性中
  14. // connect 方法返回一个函数,该函数需要接收一个组件(需要使用到 store 状态的组件)
  15. export default connect(mapStateToProps)(Counter)

优化1
在 Counter 组件中写了 onClick 代码太长,影响了组件代码的可读性,我们期望在点击按钮的时候,通过一个函数来触发 action
比如:<button onClick={decrement}>-</button>

要如何实现这个需求呢?

此时我们就需要用到 connect 方法的第二个参数,它也是函数,该函数可以拿到 store 的 dispatch 方法

  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. function Counter({ count, decrement, increment}) {
  4. return <div>
  5. <button onClick={decrement}>-</button>
  6. <span>{count}</span>
  7. <button onClick={increment}>+</button>
  8. </div>
  9. }
  10. const mapStateToProps = (state) => ({
  11. count: state.count
  12. })
  13. const mapDispatchToProps = (dispatch) => ({
  14. decrement() {
  15. return dispatch({ type: 'decrement' })
  16. },
  17. increment() {
  18. return dispatch({ type: 'increment' })
  19. },
  20. })
  21. // connect 方法第一个参数接收一个函数,这个函数的参数是 store 中的 state,返回一个对象,这个对象会被映射到组件的 props 属性中
  22. // connect 方法第二个参数接收一个函数,这个函数的参数是 store 中的 dispatch 方法,返回一个对象,这个对象会被映射到组件的 props 属性中
  23. // connect 方法返回一个函数,该函数需要接收一个组件(需要使用到 store 状态的组件)
  24. export default connect(mapStateToProps, mapDispatchToProps)(Counter)

优化2
通过观察 Counter 组件的代码发现,mapDispatchToProps 函数里面还是有重复的代码,我们接着优化它

这里通过 redux 提供的一个方法 —— bindActionCreators
bindActionCreators 方法用于创建 dispatch 的 actions,它接收两个参数,第一个参数是 actions 对象,第二个参数是 dispatch。返回一个生成好的 dispatch 对象

新建一个文件:src/store/actions/counter.actions.js

  1. export const increment = () => ({ type: 'increment' })
  2. export const decrement = () => ({ type: 'decrement' })

在 Counter 组件中导入 counter actions 并传入给 bindActionCreators 方法

  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import { bindActionCreators } from 'redux';
  4. import * as counterActions from '../store/actions/counter.actions';
  5. ...
  6. const mapDispatchToProps = (dispatch) => bindActionCreators(counterActions, dispatch)
  7. // connect 方法接收一个函数,这个函数的参数是 store 中的 state,返回一个对象,这个对象会被映射到组件的 props 属性中
  8. // connect 方法返回一个函数,该函数需要接收一个组件(需要使用到 store 状态的组件)
  9. export default connect(mapStateToProps, mapDispatchToProps)(Counter)

2.6 为 action 传递参数

  1. 组件中点击时,给 action 传参

    1. function Counter({ count, decrement, increment }) {
    2. return (
    3. <div>
    4. <button onClick={() => decrement(5)}>-</button>
    5. <span>{count}</span>
    6. <button onClick={() => increment(5)}>+</button>
    7. </div>
    8. );
    9. }
  2. action 中接收 payload 参数,并传递给 reducer

    1. export const increment = (payload) => ({ type: INCREMENT, payload });
    2. export const decrement = (payload) => ({ type: DECREMENT, payload });
  3. reducer 接收到 action 形参,并使用 payload

    1. const reducer = (state = initialState, action) => {
    2. switch (action.type) {
    3. case INCREMENT:
    4. return { count: state.count + action.payload };
    5. case DECREMENT:
    6. return { count: state.count - action.payload };
    7. default:
    8. return state;
    9. }
    10. };

2.7 拆分合并 reducer

在一个真实项目中,可能会触发多个 reducer,如果将所有 reducer 都放在同一个 reducer 文件中的话,就会变得臃肿,不可维护。所以我们应当将不同模块的 reducer 拆分出来,然后再组合起来。

拆分

modal.reducer.js

  1. import { HIDE_MODAL, SHOW_MODAL } from '../const/modal.const';
  2. const initialState = { show: false };
  3. const reducer = (state = initialState, action) => {
  4. switch (action.type) {
  5. case SHOW_MODAL:
  6. return { ...state, show: true };
  7. case HIDE_MODAL:
  8. return { ...state, show: false };
  9. default:
  10. return state;
  11. }
  12. };
  13. export default reducer;

count.reducer.js

  1. import { DECREMENT, INCREMENT } from '../const/counter.const';
  2. const initialState = { count: 0 };
  3. const reducer = (state = initialState, action) => {
  4. switch (action.type) {
  5. case INCREMENT:
  6. return { ...state, count: state.count + action.payload };
  7. case DECREMENT:
  8. return { ...state, count: state.count - action.payload };
  9. default:
  10. return state;
  11. }
  12. };
  13. export default reducer;

每一个模块的 reducer 的结构都是相同的

合并
使用 redux 提供的 combineReducers 函数,将多个小的 reducer 组合在一起

root.reducer.js

  1. import { combineReducers } from 'redux';
  2. import counterReducer from './counter.reducer';
  3. import modalReducer from './modal.reducer';
  4. // { counter: { count: 0 }, modal: { show: false } }
  5. export default combineReducers({
  6. counter: counterReducer,
  7. modal: modalReducer
  8. });

组件中使用

  1. const mapStateToProps = (state) => ({
  2. showState: state.modal.show
  3. });
  4. const mapStateToProps = (state) => ({
  5. count: state.counter.count
  6. });

3. Redux 中间件

目标:搞懂什么是 Redux 中间件,以及工作中常用的 Redux 中间件的使用

3.1 什么是中间件

中间件本质上是一个函数,Redux 允许我们使用中间件的方式扩展和增强 React 应用程序,而这个增强则体现在对 action 的处理能力上。
前面在处理 action 的时候,action 是直接交给 reducer 去处理的;而如果加上了中间件的话, action 会优先被中间件处理,然后再被 reducer 处理

3.2 加上中间件之后 React 的工作流程:

image.png

3.3 开发 Redux 中间件

中间件的模板:

  1. export default store => next => action => {
  2. console.log(store);
  3. console.log(action);
  4. next(action)
  5. }

由模板可以看出:

  1. Redux 中间件是一个函数(柯里化函数),这个函数要求返回一个函数,在返回的函数中,要求再返回一个函数,而在最里层的函数中可以执行自己的业务逻辑
  2. 最外层的函数提供一个参数 store,可以通过 store.getState() 方法获取当前这个 store 的状态,通过 store.dispatch() 方法去触发另一个 action
  3. 最里层的函数也有一个形参 action,这个 action 则是组件触发的那个 action,可以通过判断 action.type 来决定是否对这个 action 进行处理,或者进行什么样的处理
  4. 中间层的函数也有一个参数 next,它是一个函数,当最里层的中间件逻辑执行完之后,需要调用 next,将当前 action 传递给 reducer 或者说传递给下一个中间件。next(action);

3.4 注册中间件

中间件只有被注册到 Store 之后才会在 Redux 的工作流程中生效

  1. import { applyMiddleware, createStore } from 'redux';
  2. import logger from './middleware/logger';
  3. import reducer from './reducers/root.reducer';
  4. const store = createStore(
  5. reducer,
  6. /* 这里本来是 reducer 的默认参数,但是它是可选的, 所以可以将 applyMiddleware 放在第二个参数上 */
  7. applyMiddleware(logger)
  8. );
  9. export default store;

Redux 中提供了 applyMiddleware 函数,它可以作为 createStore 函数的第三个参数,但由于 createStore 的第二个参数是可选的,所以可以将它放在第二个参数;applyMiddleware 接收中间件作为参数

多个中间件如何应用?

middleWare/test.js

export default store => next => action => {
  console.log('test 中间件被执行了')
  // 一定要调用next方法,这样才能将action传递给下一个中间件或者reducer
  next(action)
}
import { applyMiddleware, createStore } from 'redux';
import logger from './middleware/logger';
import test from './middleware/test';
import reducer from './reducers/root.reducer';

const store = createStore(
  reducer,
  applyMiddleware(logger,test)
);

export default store;

中间件的执行顺序:取决于传入 注册的顺序,也**applyMiddleware** 函数的参数顺序

假设我们要延迟加或者减的操作,应该如何去实现呢?
实现思路:

  1. 使用中间件实现
  2. 通过中间件的最后一个参数action的type判断是否为增减操作,如果是,就延迟传递action给reducer

(也就是延迟执行next(action)方法)

middleWare/thunk.js

export default store => next => action => {
  const {type} = action;
  if(['increment','decrement'].includes(type)){
    setTimeout(()=>{
      next(action)
    },2000)
  }else{
    next(action)
  }
}

注册 thunk

import { applyMiddleware, createStore } from 'redux';
import logger from './middleware/logger';
import test from './middleware/test';
import thunk from './middleware/thunk';
import reducer from './reducers/root.reducer';

const store = createStore(
  reducer,
  applyMiddleware(logger,test,thunk)
);

export default store;

到此延迟加减的功能已经实现了,但是这种代码书写方式并不好,不灵活。
比如说我们要在弹框中加入异步操作,在显示和隐藏这两个按钮加入延时操作,是不是又得写一个相似的中间件了。如何写得通用一些?

3.5 中间件添加异步操作

  1. 当前中间件不关心执行的是什么样的异步操作,只关心你执行的是不是一个异步操作
  2. 如果你执行的是异步操作,在触发 action 的时候,传递一个函数;如果是同步操作,就传递一个 action 对象即可
  3. 异步操作代码要写在 传递过来的函数中
  4. 当前中间件会调用你传递进来的函数,并将 dispatch 方法传递过去
  5. 当你的异步函数执行完成之后,去调用 dispatch 方法,去触发另一个 action,并向这个 action 传递参数,再传递给 reducer。这样就可以把异步结果传递给 reducer 中了

thunk.js

const thunk = ({ dispatch }) => (next) => (action) => {
  if (typeof action === 'function') {
    return action(dispatch);
  }

  next(action);
};

export default thunk;

counter.action.js

import { DECREMENT, INCREMENT } from '../const/counter.const';

export const increment = (payload) => ({ type: INCREMENT, payload });
export const decrement = (payload) => ({ type: DECREMENT, payload });

export const increment_async = (payload) => (dispatch) => {
  setTimeout(() => {
    dispatch(increment(payload));
  }, 2000);
};
  • 当我们想要执行异步操作的时候,我们在actionCreator这个地方不返回一个对象 ,而是返回一个函数
  • 我们在返回的这个函数中执行异步操作,通过调用dispatch方法触发一个其他的action
  • 通过参数将异步操作的结果传递给其他的action, 通过其他的action传递数据给reducer, reducer再处理保存这个数据。

counter.js

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter.actions';

function Counter({ count, decrement, increment, increment_async }) {
  return (
    <div>
      <button onClick={() => decrement(5)}>-</button>
      <span>{count}</span>
      <button onClick={() => increment_async(5)}>+</button>
    </div>
  );
}

const mapStateToProps = (state) => ({
  count: state.counter.count
});

const mapDispatchToProps = (dispatch) =>
  bindActionCreators(counterActions, dispatch);

// connect 方法接收一个函数,这个函数的参数是 store 中的 state,返回一个对象,这个对象会被映射到组件的 props 属性中
// connect 方法返回一个函数,该函数需要接收一个组件(需要使用到 store 状态的组件)
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

4. Redux 常用中间件

4.1 redux-thunk

redux-thunk 中间件的作用和用法与 thunk 中间件是一样的,它允许我们在使用 redux 的过程中加入异步操作,而 thunk 就是仿照 redux-thunk 来做的

  • 安装:npm install redux-thunk
  • 引入:import thunk from 'redux-thunk'
  • 注册:createStore(reducer, applyMiddleware(thunk))

4.2 redux-saga

redux-saga 与 redux-thunk 作用是一样的,都是允许我们在 redux 工作流程中加入异步代码。但是 redux-saga 更加强大,它允许我们把异步操作从 Action Creator 中抽离出来,放在一个单独的文件中

  • 安装:npm install redux-saga
  • 引入:import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware();
  • 注册:createStore(reducer, applyMiddleware(sagaMiddleware))
import { applyMiddleware, createStore } from 'redux';
import reducer from './reducers/root.reducer';

import createSagaMiddleware from 'redux-saga'; 
const sagaMiddleware = createSagaMiddleware();

createStore(reducer, applyMiddleware(sagaMiddleware))

4.2.1 使用 saga 接收 action 执行异步操作

saga 文件中需要从 redux-saga/effects 中引入两个方法

  • takeEvery:用于接收 action,也就是说当组件去触发一个 action 的时候,saga 文件中可以通过 takeEvery 去接收这个 action

    • 第一个参数:要接收的 action 的类型字符串
    • 第二个参数:当接收到这个 action 以后需要执行的方法(也是 Generator 函数,里面写异步操作的代码)
  • put:用于触发另外一个 action,当异步操作执行完成返回结果以后,需要通过 put 方法去触发一个 action,将异步操作的结果传递给 reducer,reducer 再将数据保存到 store 中。put 的作用跟 dispatch 方法是一样的

在 saga 文件中,要求默认导出一个 generator 函数

import { takeEvery, put } from 'redux-saga/effects';

function* load_posts() {
  const { data } = yield axios.get('/api/posts.json');
  yield put(load_posts_success(data));
}

export default function* postSaga() {
    yield takeEvery(LOAD_POSTS, load_posts);
}

4.2.2 启用 saga

引入 saga 文件之后,需要使用 sagaMiddleware.run() 方法启用 saga 文件

import postsSaga from './store/sagas/posts.saga';

sagaMiddleware.run(postsSaga);

4.2.3 示例

components/counter.jsx

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter.actions';

function Counter({ count, decrement, increment, increment_async }) {
  return (
    <div>
      <button onClick={() => decrement(5)}>-</button>
      <span>{count}</span>
      <button onClick={increment_async}>+</button>
    </div>
  );
}

const mapStateToProps = (state) => ({
  count: state.counter.count
});

const mapDispatchToProps = (dispatch) =>
  bindActionCreators(counterActions, dispatch);

// connect 方法接收一个函数,这个函数的参数是 store 中的 state,返回一个对象,这个对象会被映射到组件的 props 属性中
// connect 方法返回一个函数,该函数需要接收一个组件(需要使用到 store 状态的组件)
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

store/sagas/counterSaga.js

import { delay, put, takeEvery } from 'redux-saga/effects';
import { increment } from '../actions/counter.actions';
import { INCREMENT_ASYNC } from '../const/counter.const';

function* increment_async_fn() {
  yield delay(2000); // delay 是 redux-saga 提供的延迟函数
  yield put(increment(10));
}

export default function* counterSaga() {
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn);
}

store/index.js

import { applyMiddleware, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers/root.reducer';
import counterSaga from './sagas/counterSaga';

const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  reducer,
  /* 这里本来是 reducer 的默认参数,但是它是可选的, 所以可以将 applyMiddleware 放在第二个参数上 */
  applyMiddleware(sagaMiddleware)
);

sagaMiddleware.run(counterSaga);

export default store;

4.2.4 redux-saga 传参

// component
<button onClick={()=>increment_async(20)}>+</button>

// action
export const increment_async = (payload) => ({ type: INCREMENT_ASYNC, payload });

// saga
function* increment_async_fn(action) {
  yield delay(2000);
  yield put(increment(action.payload));
}
export default function* counterSaga() {
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn);
}

4.2.5 redux-saga 的拆分和合并

counter.saga.js

import { delay, put, takeEvery } from 'redux-saga/effects';
import { increment } from '../actions/counter.actions';
import { INCREMENT_ASYNC } from '../const/counter.const';

function* increment_async_fn(action) {
  yield delay(2000);
  yield put(increment(action.payload));
}

export default function* counterSaga() {
  yield takeEvery(INCREMENT_ASYNC, increment_async_fn);
}

modal.saga.js

import { delay, put, takeEvery } from 'redux-saga/effects';
import { show } from '../actions/modal.action';
import { SHOW_MODAL_ASYNC } from '../const/modal.const';

function* showModal_async_fn() {
  yield delay(2000);
  yield put(show());
}

export default function* modalSaga() {
  yield takeEvery(SHOW_MODAL_ASYNC, showModal_async_fn);
}

root.saga.js

import { all } from 'redux-saga/effects';
import counterSaga from './counter.saga';
import modalSaga from './modal.saga';

function* rootSaga() {
  // all 方法传入数组,数组元素为 saga 的调用
  yield all([counterSaga(), modalSaga()]);
}

export default rootSaga;

最后将 rootSaga 传入 saga.run 方法中

import { applyMiddleware, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers/root.reducer';
import rootSaga from './sagas/root.saga';

const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  reducer,
  /* 这里本来是 reducer 的默认参数,但是它是可选的, 所以可以将 applyMiddleware 放在第二个参数上 */
  applyMiddleware(sagaMiddleware)
);

sagaMiddleware.run(rootSaga);

export default store;

4.3 redux-actions

redux-actions 可以简化 Action 和 Reducer 的使用

  • 安装:npm install redux-actions
  • 引入:import { createAction } from 'redux-actions';
  • 注册:

4.3.1 createAction 方法的使用

  1. createAction 是 redux-actions 中间件提供的一个方法,用于简化 action。它接收一个字符串,这个字符串是 action 对象里面的 TYPE 属性值;它的返回值就是 actionCreator 函数
import { createAction } from 'redux-actions';

export const increment = createAction('increment');
export const decrement = createAction('decrement');
  1. 使用 createAction 方法创建 actionCreator 的好处:在创建和触发 action 的时候,只需要使用 createAction 方法的返回值就可以了,不需要再将 action 的 TYPE 值抽象成常量

4.3.2 handleActions 方法的使用

  1. handleActions 方法的作用就是:接收 action,处理 action,实际上这个方法的返回值就是之前我们自己创建的 reducer 函数
import { handleActions as createReducer } from 'redux-actions';
import { decrement, increment } from '../actions/counter.actions';

const initialState = { count: 0 };

const handleIncrement = (state, action) => ({
  count: state.count + action.payload
});
const handleDecrement = (state, action) => ({
  count: state.count - action.payload
});

export default createReducer(
  {
    [increment]: handleIncrement,
    [decrement]: handleDecrement
  },
  initialState
);