(28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

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

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

javascript 复制代码
涉及面试题:
1. 如何在 Redux 中发起 AJAX 请求?
2. 什么是 Redux Thunk?

编号:[react_28]

1 安装和配置 Redux-thunk

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

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

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

javascript 复制代码
npm install redux-thunk

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

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

import reducer from "./reducer"; 

import thunk from "redux-thunk"; // 1️⃣-④:从 redux-thunk 库中引入 thunk 模块;

const store = createStore(
  reducer,
  
  /*
  1️⃣-⑤:当创建 store 的时候,
  需要在 store 的第二个参数里边填写 applyMiddleware(thunk);
   */
  applyMiddleware(thunk)
  
  /*
  1️⃣-⑥:❓❓❓可下边这行代码应该怎么放置呢?即,我们既想用 redux-thunk "中间件",
  又想用到 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 thunk from "redux-thunk";  

// 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(thunk) /*
  											 1️⃣-⑭:顺便把 thunk 通过 applyMiddleware 执行一下(
                         ❗️❗️❗️官方文档里是 ...middleware,但是我们项目代码里
                         并没有 middleware 这个"数组"变量,有的只是 thunk 
                         这一个"中间件"。故,用 thunk 替换掉 ...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; 

看看页面效果(代码正常运行,redux devtools 插件也正常可用):

2 围绕 Redux-thunk 来编写代码

打开 TodoList.js 文件:

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

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

import TodoListUI from "./TodoListUI"; 

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() { 
    
    /*
    2️⃣-①:为什么要用 Redux "中间件",
    就是为了能把"异步"和"复杂"逻辑代码放在专门的位置进行编写和管理。
    这里,我们就可以把以下 AJAX 请求数据相关的代码放到 action 中!
    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;

打开 actionCreators.js 文件:

javascript 复制代码
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION} from "./actionTypes";  

import axios from "axios"; // 2️⃣-⑤:在这里引入 axios;

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 
})


export const getTodoList = () => {
  return () => { // ❗️2️⃣-②:因为用了"中间件",所以 action 可以以"函数"的形式返回出来了;
    
    /*
    ❗️❗️❗️2️⃣-③:将"异步"的代码粘贴至此处(即,这里 getTodoList 返回出的
    不再是一个"对象",而是一个"函数"了。"函数"里边就可以去做"异步"的操作!);
     */
    axios.get("http://rap2api.taobao.org/app/mock/232799/api/todolist") /*
    																												2️⃣-④:既然这里
    																												用到了 axios,就需要
                                                            将 TodoList 里对 axios
                                                            引入相关的代码放置到本文件
                                                            中;
                                                            						 */
    .then((res) => {  
      const data = res.data.data;  
      
      console.log(data) /*
      									❗️可以在这里试着打印一下 data 的获取情况,
      									以及它到底是个什么东西?
                         */
      
      /*
      ❗️❗️❗️下边两行代码先不管,稍后再说它的逻辑和相应的改写!
      const action = initListAction(data);  
      store.dispatch(action);
       */
    })
    .catch(() => {alert("error")})    
  }
}

2️⃣-⑥:再次打开 TodoList.js 文件;

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

// 2️⃣-⑧:在这里先引入 getTodoList;
import {getTodoList, getInputChangeAction, getAddItemAction, getDeleteItemAction, initListAction} from "./store/actionCreators"; 

import TodoListUI from "./TodoListUI"; 

/*
❗️❗️❗️这里关于 axios 引入的代码,已被移至 actionCreator 中;
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() { 
    /*
    2️⃣-⑦:既然"异步"相关的代码已被移动至 getTodoList 返回的 action "函数"中,
    那这里就可以直接调用 getTodoList;(❗️注意:要使用 actionCreators 中
    定义的 getTodoList,必须记得在本文件中去引入!)
     */
    const action = getTodoList()
    
    console.log(action) /*
    										❗️2️⃣-⑨:我们可以试着在控制台打印一下这个 action,
    										 按上篇文章所说,它应该可以被正常打印,且打印出来是一个"函数"!
                         */
    
    /*
    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;

查看页面控制台打印信息(的确为我们打印出了"函数"):

2️⃣-⑩:接下来,我们就可以在 TodoList.js 文件中,将 getTodoList 返回的这个 action 发送给 store;

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


import {getTodoList, getInputChangeAction, getAddItemAction, getDeleteItemAction, initListAction} 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 = getTodoList()
    
    store.dispatch(action) // ❗️❗️❗️将 action 发送给 store;
    
    
    /*
    下面这行代码可以注释掉了!
    console.log(action)  
     */
    
    /*
    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;

查看页面控制台打印信息(数据成功接收,且在控制台打印出了 data 相关的信息):

❓这是怎样一个逻辑呢?

答:正如我们上一篇文章《Redux 进阶------③ Redux 中间件(上):初识 Redux 中间件》中所说,"Redux 中间件"是对 dispatch 方法的一个升级。

就 Redux-thunk 和本例而言,由于 getTodoList 返回的这个 action 是一个"函数",Redux-thunk 对 dispatch 方法升级的思想是:若 Action 是一个"函数",升级后的 Dispatch 方法自己知道不会将"函数"直接传递给 Store。它会让"函数"先执行,待执行完成后,如果需要调用 Store,这个"函数"再去调用。

故,数据可以被获取且打印在控制台上。

3️⃣既然获取到了"数据",我们就可以去利用这个数据了。打开 actionCreators.js 文件:

javascript 复制代码
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION} from "./actionTypes";  

import axios from "axios"; 

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 
})

export const getTodoList = () => {
  return (dispatch) => { // 3️⃣-④:返回的"函数"可以接收到 store 的 dispatch 方法;
    
    axios.get("http://rap2api.taobao.org/app/mock/232799/api/todolist") 
    .then((res) => {  
      const data = res.data.data;  
      
      console.log(data)  
      
      /*
      3️⃣-①:Redux 工作流程告诉我们,要拿获取到的"数据"去改变 store 中的数据,
      你就得重新按"流程图"走一次;
       */
      const action = initListAction(data); // 3️⃣-②:定义 action;
      
      /*
      3️⃣-③:发送 action(❗️注意:由于本文件中并没有 store 这个仓库,所以直接
      写 store.dispatch(action) 是会报错的!
      ❗️❗️❗️好的是,这里在定义 getTodoList 时,由于返回的 action 是一个"函数",
      这个"函数"可以接收到 store 的 dispatch 方法!);
       */
      dispatch(action); /*
      									3️⃣-⑤:既然接收到了 dispatch 方法,就可以直接调用这个方法
                        传递 action 给 store。
                         */
   
    })
    .catch(() => {alert("error")})    
  }
}

返回页面查看效果(我们模拟的数据被正确地显示在了"列表项"里):

有一个"警告",按照提示去 TodoList.js 文件中的"第 6 行"里,把 initListAction 删除即可。因为"异步代码"已经被我们放置在了 action 里了!

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

// ❗️❗️❗️删除这里的 initListAction!
import {getTodoList, 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 = getTodoList()
    
    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;

返回页面查看效果(正确显示,无"警告"):

祝好,qdywxs ♥ you!

相关推荐
恋猫de小郭4 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端