转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
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!