(29)Redux 进阶——⑤ Redux 中间件(下):使用 Redux-saga 中间件实现 AJAX 数据请求 | React 基础理论实操

复制代码
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

scss 复制代码
涉及面试题:
1. 什么是 Redux-saga?
2. Redux-saga 的模型概念是什么?
3. 在 Redux-saga 中 call() 和 put() 之间有什么区别?
4. Redux-saga 和 Redux-thunk 之间有什么区别?

编号:[react_29]

1 安装和配置 Redux-saga

❗️注意:为了便于讲解,请将代码恢复至《Redux 进阶------② Redux 中发送异步请求获取数据》中的版本!

1️⃣在 GitHub 中找到 Redux-saga,按照文档提示进行相关的安装和配置。

1️⃣-①:安装 Redux-saga 并重启项目;

javascript 复制代码
npm install --save redux-saga

1️⃣-②:打开创建 store 的代码文件(store 目录下的 index.js 文件),按照官方文档提示,进行相应配置;

javascript 复制代码
import { createStore, applyMiddleware } from "redux"; /*
																											1️⃣-③:从 redux 中引入
																											applyMiddleware 方法。这个方法
                                                      使得我们可以使用"中间件";
                                                       */

import reducer from "./reducer"; 

import createSagaMiddleware from "redux-saga"; /*
																							 1️⃣-④:从 redux-saga 库中引入
																							 createSagaMiddleware 方法;
                                                */

const sagaMiddleware = createSagaMiddleware(); /*
																							 1️⃣-⑤:通过 createSagaMiddleware 方法
																							 生成一个 sagaMiddleware 中间件;
                                                */

const store = createStore(
  reducer,
  
  /*
  1️⃣-⑥:当创建 store 的时候,
  需要在 store 的第二个参数里边填写 applyMiddleware(sagaMiddleware);
   */
  applyMiddleware(sagaMiddleware)
  
  /*
  1️⃣-⑦:❓❓❓可下边这行代码应该怎么放置呢?即,我们既想用 redux-saga "中间件",
  又想用到 redux-devtools-extension 这个"扩展"来方便"调试",应该怎样放置代码呢?
  
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
   */
);  

export default store; 

答:打开 redux-devtools-extension,查看官方文档。

所以,store 目录下 index.js 文件中的代码应改写为:

javascript 复制代码
// 1️⃣-⑪:将 compose 函数从 redux 中引入进来;
import { createStore, applyMiddleware, compose } from "redux";  

import reducer from "./reducer"; 

import createSagaMiddleware from "redux-saga";

const sagaMiddleware = createSagaMiddleware();

// 1️⃣-⑧:直接拷贝官方文档里的代码;
const composeEnhancers =
  /*
  ❗️1️⃣-⑨:这行代码可以注释掉,因为浏览器的应用,故 window 的 object 是肯定存在的!
  typeof window === 'object' && 
   */
  
  /*
  ❗️1️⃣-⑩:下面这行代码和之前的意思一样:
  如果 window 下边有 __REDUX_DEVTOOLS_EXTENSION__ 这个变量的话,
  就执行这个变量对应的方法 window.__REDUX_DEVTOOLS_EXTENSION__()。
  否则,
  就执行 compose 函数;
   */
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;


// 1️⃣-⑬:继续拷贝官网的代码;
const enhancer = composeEnhancers( /*
																	 1️⃣-⑭:表示将 composeEnhancers 执行后的
																	 结果赋值给 enhancer;
                                    */
  applyMiddleware(sagaMiddleware), /*
  																 1️⃣-⑮:顺便把 sagaMiddleware 通过
  																 applyMiddleware 执行一下(
                                   ❗️❗️❗️官方文档里是 ...middleware,但是我们项目代码里
                                   并没有 middleware 这个"数组"变量,有的只是 sagaMiddleware 
                                   这一个"中间件"。故,用 sagaMiddleware 替换掉 ...middleware );
                                    */
);

const store = createStore(
  reducer,
  
  /*
  1️⃣-⑯:继而,我们在创建 store 的时候,就不再使用 applyMiddleware(thunk) 
  这种语法了,故注释掉:
  applyMiddleware(thunk)
   */
  enhancer // 1️⃣-⑰:取而代之,直接将 enhancer 传递进来即可!
  
  
  /*
  1️⃣-⑫:相应地删除下面这行代码,因为 
  redux-devtools-extension 相关的代码被配置在了 1️⃣-⑨ 中;
  
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  */
);  

export default store; 

1️⃣-⑱:注意看 Redux-saga 官方文档;

在 store 目录下的 index.js 文件里,它从外部 saga.js 文件中引入了一个 mySaga

在之前的文章《Redux 进阶------③ Redux 中间件(上):初识 Redux 中间件》中,"Redux-saga 中间件"相较于"Redux-thunk 中间件"的特别之处在于:

  • Redux-thunk 是把"异步"操作放在 Action 里;
  • Redux-saga 则是把"异步"的逻辑拆分出来,单独放在一个文件里进行管理。

1️⃣-⑲:故,我们需要在 store 目录下新建一个 sagas.js 文件;

创建好 sagas.js 文件后,在 store 目录下的 index.js 文件中就可以引入了;

javascript 复制代码
import { createStore, applyMiddleware, compose } from "redux";  

import reducer from "./reducer"; 

import createSagaMiddleware from "redux-saga";

/*
1️⃣-⑳:从当前目录下的 sagas.js 文件中引入 todoSagas;
❗️❗️❗️之所以在不知道 sagas.js 是什么内容的情况下,就可以在这里导入 todoSagas,
这是由于 ES6 中通过 export default 语法导出的,导入时可以对其进行任意命名!
 */
import todoSagas from "./sagas"

const sagaMiddleware = createSagaMiddleware();

const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers( 
  applyMiddleware(sagaMiddleware)
);

const store = createStore(
  reducer,
  enhancer  
);  

/*
1️⃣-㉑:当使用了这个"中间件"之后,按照官方文档指示,
我们还要调用 sagaMiddleware 的方法 run,让 todoSagas 执行起来!
 */
sagaMiddleware.run(todoSagas)

export default store; 

2️⃣既然平白无故地创建了一个 sagas.js 文件,那么文件里总得有点东西,不然程序去运行什么呢?

继续查看 Redux-saga 的官方文档, sagas.js 是长成这样的:

当然,我们的 TodoList 项目很简单,用不了上图中那么多 API,不过必要的代码一行也不能少。打开 sagas.js 文件:

javascript 复制代码
/*
2️⃣-①:sagas.js 文件要求必须使用 ES6 中"generators 函数"的写法!
这里定义一个 mySaga 函数,然后最后导出这个函数;
 */
function* mySaga() {
  
  /*
  ❗️这行代码先不管,稍后根据项目具体逻辑进行改写!
  yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
   */
}

export default mySaga;

2️⃣-②:看看页面效果(代码正常运行,redux devtools 插件也正常可用);

2 围绕 Redux-saga 来编写代码

3️⃣打开 TodoList.js 文件:

jsx 复制代码
import React, {Component} from "react";
import 'antd/dist/antd.css';
import store from "./store";

/*
3️⃣-④:在这里引入 getInitList;
❗️同时,既然本文件中的"异步"代码已移走,initListAction 也可以删除掉了!
 */
import {getInitList, getInputChangeAction, getAddItemAction, getDeleteItemAction} from "./store/actionCreators"; 

import TodoListUI from "./TodoListUI"; 

/*
❗️相应地移除本文件对 axios 的引用!
import axios from "axios";
 */

class TodoList extends Component {
  constructor(props) {
    super(props);
    
    this.state = store.getState();
    
    this.handleInputChange = this.handleInputChange.bind(this);  
    
    this.handleStoreChange = this.handleStoreChange.bind(this);  
    
    this.handleButtonClick = this.handleButtonClick.bind(this); 

    this.handleItemDelete = this.handleItemDelete.bind(this);
    
    store.subscribe(this.handleStoreChange);  
    
  }
  
  render() {
    return(
      <TodoListUI
        inputValue={this.state.inputValue}
        list={this.state.list}  
        handleInputChange={this.handleInputChange}
        handleButtonClick={this.handleButtonClick}
        handleItemDelete={this.handleItemDelete}
      />
    )
  }
  
  componentDidMount() { 
    /*
    3️⃣-②:不在这里写"异步"代码的同时,我们遵循"Redux 工作流程"
    去定义一个 action:
     */
    const action = getInitList(); /*
    															3️⃣-③:注意我们 action 是被拆分到 actionCreators
    															里返回出来的,故现在本文件顶部适当位置引入 getInitList,
                                  然后记得在 actionCreators 中去定义这个 getInitList。
                                  ❗️同时,请注意 actionTypes 也是拆分出来单独写的,故
                                  需要在 actionTypes 中去定义"常量"!
                                   */
    
    /*
    3️⃣-①:为什么要用 Redux "中间件",
    就是为了能把"异步"和"复杂"逻辑代码放在专门的位置进行编写和管理。
    这里,我们就可以把以下 AJAX 请求数据相关的代码放到 sagas.js 中!
    axios.get("http://rap2api.taobao.org/app/mock/232799/api/todolist")  
    
    .then((res) => {  
      const data = res.data.data;  
      const action = initListAction(data);  
      store.dispatch(action);
    })
    .catch(() => {alert("error")})
     */

  } 
  
  handleInputChange(e) { 

    const action = getInputChangeAction(e.target.value)
    
    store.dispatch(action);  
  }

  handleStoreChange() { 
    
    this.setState(store.getState()); 
  }


  handleButtonClick() { 
    
    const action = getAddItemAction();  
    
    store.dispatch(action); 
  }

  handleItemDelete(index) { 

    const action = getDeleteItemAction(index);
    
    store.dispatch(action); 
  } 

}

export default TodoList;

3️⃣-⑤:先在 actionTypes.js 中定义"常量" GET_INIT_LIST

javascript 复制代码
export const CHANGE_INPUT_VALUE = "change_input_value";
export const ADD_TODO_ITEM = "add_todo_item";
export const DELETE_TODO_ITEM = "delete_todo_item";
export const INIT_LIST_ACTION = "init_list_action";

// ❗️定义常量 GET_LIST_ACTION;
export const GET_INIT_LIST = "get_init_list"

3️⃣-⑥:再在 actionCreators.js 中去定义 getInitList

javascript 复制代码
// 3️⃣-⑦:引入"常量"GET_INIT_LIST;
import {GET_INIT_LIST, CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION} from "./actionTypes";  

export const getInputChangeAction = (value) => ({ 
  type: CHANGE_INPUT_VALUE, 
  value  
});

export const getAddItem = () => ({
  type: ADD_TODO_ITEM
});

export const getAddItemAction = () => ({
  type: ADD_TODO_ITEM
});

export const getDeleteItemAction = (index) => ({
  type: DELETE_TODO_ITEM,
  index
})

export const initListAction = (data) => ({  
  type: INIT_LIST_ACTION,  
  data  
})

/*
3️⃣-⑧:定义 getInitList;
❗️❗️❗️注意:既然我们没有用 Redux-thunk,那么返回的 action 就还得是"对象"的形式!
*/
export const getInitList = () => ({
  type: GET_INIT_LIST
})

返回 TodoList.js 文件,action 定义好后,就应该调用 store 的 dispatch 方法,将 action 传递出去;

jsx 复制代码
import React, {Component} from "react";
import 'antd/dist/antd.css';
import store from "./store";

import {getInitList, getInputChangeAction, getAddItemAction, getDeleteItemAction} from "./store/actionCreators"; 

import TodoListUI from "./TodoListUI"; 

class TodoList extends Component {
  constructor(props) {
    super(props);
    
    this.state = store.getState();
    
    this.handleInputChange = this.handleInputChange.bind(this);  
    
    this.handleStoreChange = this.handleStoreChange.bind(this);  
    
    this.handleButtonClick = this.handleButtonClick.bind(this); 

    this.handleItemDelete = this.handleItemDelete.bind(this);
    
    store.subscribe(this.handleStoreChange);  
    
  }
  
  render() {
    return(
      <TodoListUI
        inputValue={this.state.inputValue}
        list={this.state.list}  
        handleInputChange={this.handleInputChange}
        handleButtonClick={this.handleButtonClick}
        handleItemDelete={this.handleItemDelete}
      />
    )
  }
  
  componentDidMount() { 
    const action = getInitList();  
    
    /*
    ❗️❗️❗️3️⃣-⑨:将 action 传递出去;
    而这一步就是 Redux-saga 最重要的一步:
    之前,我们没用 Redux-saga 等任何"中间件"时,按照 Redux 工作流程,
    这里的 action 只会传递给 store,然后 store 再拿着之前的 state 和这里的 action 
    传递给 reducer;
    但现在,使用了 Redux-saga 后,除了和上边一样,在 reducer 中能接收到 action 外,
    sagas.js 中也能接收到 action!
     */
    store.dispatch(action);
  } 
  
  handleInputChange(e) { 

    const action = getInputChangeAction(e.target.value)
    
    store.dispatch(action);  
  }

  handleStoreChange() { 
    
    this.setState(store.getState()); 
  }


  handleButtonClick() { 
    
    const action = getAddItemAction();  
    
    store.dispatch(action); 
  }

  handleItemDelete(index) { 

    const action = getDeleteItemAction(index);
    
    store.dispatch(action); 
  } 

}

export default TodoList;

3️⃣-⑩:既然使用了 Redux-saga 后,action 可以被传递给 sagas.js 文件,那我们就可以在 sagas.js 文件中完成"异步"代码逻辑的编写;

javascript 复制代码
// 3️⃣-⑲:引入 put 方法;
import { takeEvery, put } from "redux-saga/effects"; /*
																									3️⃣-⑪:首先,从 redux-saga 下边的
																								  effects 里引入 takeEvery 方法;
                                                  		*/
																									
import { GET_INIT_LIST } from "./actionTypes"; /*
																							 3️⃣-⑫:我们本意就是想让"类型"为 
																							 GET_INIT_LIST 的 action 在本文件中
                                               被"捕捉"到。所以,传入进来以便使用;
                                                */

import axios from "axios";  /*❗️引入 axios;*/

import { initListAction } from "./actionCreators";  /*3️⃣-⑰:引入 initListAction;*/

function* getInitList() { /*
													3️⃣-⑭:这里就和 reducer 的感觉很像了,上一步判断了"类型",
 													这一步就执行类型对应的"函数"。
                          ❗️所以,这里就是我们放置"异步"逻辑代码的地方!
                           */
  
  try { /*
  			🏆🏆🏆:用这种"try/catch 语法"可以很好地
        展示出"数据"获取成功和失败对应的逻辑;
         */
  	
    /*
    3️⃣-⑮:generator 函数里,作"异步"请求时,不要用 promise 语法,
    要直接采用下边这种形式(❗️里边的 yield 表示------会等待 axios 
    获取数据完毕后,再把结果存在 res 里!):
    
    (❗️这里用了 axios,那么请记得将 TodoList 里关于 axios 引入的代码移除至本文件中!)
     */
    const res = yield axios.get("http://rap2api.taobao.org/app/mock/232799/api/todolist");
    
    
    /*
    3️⃣-⑯:既然"数据"获取到了,就可以拿获取到的"数据"去改变 store 里的数据了。
    按照 Redux 工作流程,我们创建一个 action,并将 res.data.data 作为"参数"传入;
    
    ❗️注意,请一定记得将 initListAction 从 actionCreators 中引入进来!
     */
    const action = initListAction(res.data.data);
    
    
    /*
    3️⃣-⑱:按流程,这里就应该调用 store 的 dispatch 方法,把 action 传递给 store 了。
    但,本文件中显然是没有 store 的。
    基于此,Redux-saga 底层为我们封装了一个 put 方法,其效果等同于 store.dispatch,
    但 put 可以追溯调用的信息,是更优的选择!
    
    ❗️❗️❗️要用 put,请一定记得在本文件中引入 put!
     */
    yield put(action); /*
    									 ❗️这里的 yield 也是表示:让 put(action) 
                       这个 action 处理过程完成后,再进行下一步的操作!
                        */
    
  } catch(e) { //❗️这里就表示:若 AJAX 获取数据失败,应该怎么办!
    console.log("获取数据失败~")  
  }

}


function* mySaga() {
  yield takeEvery(GET_INIT_LIST, getInitList); /*
  																						 ❗️❗️❗️3️⃣-⑬:然后,按照官方文档提示,
  																						 用 yield 语法,调用 takeEvery 
                                               方法,去"捕捉每一个"传递过来的 
                                               action 的"类型",一旦"类型"是
                                               GET_INIT_LIST,就执行 getInitList。
                                                */
}

export default mySaga;

返回页面查看效果(代码一切正常运行):

祝好,qdywxs ♥ you!

相关推荐
你的人类朋友5 分钟前
🫏光速入门cURL
前端·后端·程序员
01传说28 分钟前
vue3 配置安装 pnpm 报错 已解决
java·前端·vue.js·前端框架·npm·node.js
Misha韩34 分钟前
React Native 一些API详解
react native·react.js
小李飞飞砖34 分钟前
React Native 组件间通信方式详解
javascript·react native·react.js
小李飞飞砖35 分钟前
React Native 状态管理方案全面对比
javascript·react native·react.js
烛阴2 小时前
Python装饰器解除:如何让被装饰的函数重获自由?
前端·python
千鼎数字孪生-可视化2 小时前
Web技术栈重塑HMI开发:HTML5+WebGL的轻量化实践路径
前端·html5·webgl
凌辰揽月2 小时前
7月10号总结 (1)
前端·css·css3
天天扭码2 小时前
很全面的前端面试——CSS篇(上)
前端·css·面试
EndingCoder2 小时前
搜索算法在前端的实践
前端·算法·性能优化·状态模式·搜索算法