likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 10
转载请注明出处,未经同意,不可修改文章内容。

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

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

编号:[react_28]

1 安装和配置 Redux-thunk

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

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

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

npm install redux-thunk

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

1️⃣-②:打开创建 store 的代码文件(store 目录下的 index.js 文件),按照官方文档提示,进行相应配置; (28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

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,查看官方文档。 (28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

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

// 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 插件也正常可用): (28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

2 围绕 Redux-thunk 来编写代码

打开 TodoList.js 文件:

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 文件:

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 文件;

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;

查看页面控制台打印信息(的确为我们打印出了“函数”): (28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

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

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 相关的信息): (28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

❓这是怎样一个逻辑呢? 答:正如我们上一篇文章《Redux 进阶——③ Redux 中间件(上):初识 Redux 中间件》中所说,“Redux 中间件”是对 dispatch 方法的一个升级。

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

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

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

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

返回页面查看效果(我们模拟的数据被正确地显示在了“列表项”里): (28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

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

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;

返回页面查看效果(正确显示,无“警告”): (28)Redux 进阶——④ Redux 中间件(中):使用 Redux-thunk 中间件实现 AJAX 数据请求 | React 基础理论实操

祝好,qdywxs ♥ you!