最近在做的一个项目使用的是 react + redux + antd,有一个需求是需要在发送请求时,出现遮罩层,当数据请求成功后,遮罩层消失。由于项目有很多组件,每一个组件也会有很多请求接口。在每次请求的时候设置属性控制遮罩层的显示自然是可以实现,但是这样对于一个前端来说一点都不优美,而且在项目中必定会出现很多接口同时请求的,那这不就更复杂了!!!
当然,我们必须做一个优美的前端工程师啦!于是,根据自己的想法和在网上查了一些资料。我决定,使用 antd 的 spin 组件的延迟效果,加上 redux 实现全局设置。
//开启loading
export const openLoading = ()=>({
type:OPENLOADING,
})
//关闭loading
export const closeLoading = ()=>({
type:CLOSELOADING,
})
export const OPENLOADING = "openLoading"
export const CLOSELOADING = "closeLoading"
import { OPENLOADING, CLOSELOADING } from "./actionType";
let defaultState = {
loading:false,
};
export function todoReducer(state = defaultState, action) {
let newObj = { ...state };
switch (action.type) {
case OPENLOADING: {
newObj.loading = true;
return newObj;
}
case CLOSELOADING: {
newObj.loading = false;
return newObj;
}
default:
return state;
}
}
温馨提示:如果对以上步骤不清楚明白的,可以先提前了解 redux 的使用,这是基础的 redux 使用方法
这个意思就是,哪些组件需要用到,我们就把 spin 组件包住它。是不是好像还是没有说清楚,好的,我就用我现在的项目举一个例子吧!
项目中,登录页并不需要使用 loading,而是在登陆成功之后到达的页面开始全需要在请求时使用 loading。
如下图,我的 XXX 组件中包含了我需要用到 loading 的组件路由:
import { store } from '../../redux/store.js'
import { Spin } from 'antd'
export default class XXX extends Component {
state = {
loading: false
}
componentDidMount () {
// 注册(订阅)监听, 一旦状态发生改变, 自动重新渲染
store.subscribe(() => {
this.setState({
loading: store.getState().loading
})
})
}
render() {
return (
<Spin spinning={this.state.loading}>
<Route ... />
...
<Route ... />
</Spin>
);
}
}
没错就是这么简单,这一步完成之后就差不多完成一半了,接下来也是最重要的另一半。
在项目中,我使用到的是 axios
1、一个计数器:正如前言所说的,有很多请求是同时发起的,但是结束就不一定一起结束了,这时候我们需要在请求都结束之后,才能取消 loading
2、请求的拦截:需要在发起请求时,打开 loading ; 请求超时,关闭 loading
3、响应的拦截:得到响应时,关闭 loading
import axios from "axios";
import { message } from "antd";
import { store } from "../../redux/store";
import { openLoading, closeLoading } from "../../redux/actions";
let Axios = axios.create({
// timeout: 5000
});
// 计数器
let requestCount = 0;
// 展示 loading
function showLoading() {
if (requestCount === 0) {
store.dispatch(openLoading());
}
requestCount++;
}
// 隐藏 loading
function hideLoading() {
if (requestCount <= 0) return;
requestCount--;
if (requestCount === 0) {
store.dispatch(closeLoading());
}
}
// 删除 isLoading 字段 // 特别强调!!! 看下文
function deleteLoading(obj) {
let newObj = { ...obj };
delete newObj.isLoading;
return newObj;
}
// 请求拦截
Axios.interceptors.request.use(
(config) => {
if (config.isLoading !== false) {
showLoading();
}
return deleteLoading(config);
},
(error) => {
if (error.isLoading !== false) {
hideLoading();
}
message.error("请求超时");
return Promise.reject(deleteLoading(error));
}
);
// 响应拦截
Axios.interceptors.response.use(
(response) => {
if (response.config.isLoading !== false) {
hideLoading();
}
let { data } = response;
if (data.code === 0) {
return data;
}
},
(error) => {
if (error.config.isLoading !== false) {
hideLoading();
}
message.error("服务错误");
return Promise.reject(error);
}
);
export default Axios;
import axios from 'axios';
import {store} from "../../redux/store"
import Axios from "./axios.js"
const http={}
/**
* GET
* @param {*} url
* @param {*} data
* @param {*} isLoading true:使用遮罩层 false:不使用
* @returns
*/
http.get = (url, data,isLoading=true) => {
return Axios({
method: 'get',
url: url,
params: data,
isLoading: isLoading, // 特别强调!!! 看下文
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${store.getState().token}`,
},
})
};
/**
* POST
* @param {*} url
* @param {*} data
* @param {*} isLoading true:使用遮罩层 false:不使用
* @returns
*/
http.post = (url, data, isLoading=true) => {
return Axios({
method: 'post',
url: url,
data: data,
isLoading: isLoading,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${store.getState().token}`,
},
})
};
特别强调!!!
以上两处特别强调是相关联的,因为在我的需求中,组件中的有些请求是不需要使用 loading 的,所以我会在封装 axios 中自定义添加一个属性 isloading 来判断是否需要使用 loading, 判断之后我会将这个自定义的属性删除,如果后端在不知情的情况下没有做配置,服务器会不承认你自定义的这个字段,会导致说出现一些报错等情况,所以,我就干脆在我需要这个字段的时候使用它,不需要它了我就它删除!
import http from "../../server/http";
import api from "../../server/interface"; //接口文档
http.get(api, data).then((res) => {
console.log(res.data);
});
import http from "../../server/http";
import api from "../../server/interface"; //接口文档
http.get(api, data,false).then((res) => {
console.log(res.data);
});
这样就完成了 loading 全局设置使用!