这天开开心心的在家里写着毕设,想完成这样一个需求,当我们在输入框输入内容的时候,当还需要连续输入的时候,我们这个不用发送请求,只用将数据保存在前端,当用户停止输入0.5s之后,再向后端发送请求进行保存。因为现在实习公司的项目中有一个编辑笔记自动保存的功能,于是我便找到保存笔记对应的代码借鉴一下之前前辈们怎么实现的,但是结果却让我大失所望,虽然是自动保存,但是实际上就是当输入的内容改变就直接发送请求,根本就不是在最后发送一次请求,这样大大的加大了服务器的压力,于是自己想了两种方案进行解决。
一、通过防抖完成需求
防抖功能虽然在平时的项目开发中我们都是引入的第三方库进行功能的实现,但是这并不意味着我们可以不知道怎么写,所以在这里我再来巩固一下,防抖的方式也有两种,一种是将这个方法直接带有防抖功能,第二种是写一个防抖的方法,这个方法的作用是传入一个方法,以及一个防抖的时间,返回一个具有防抖功能的方法
方法一:直接将这个方法带有防抖功能
ts
const debounceFunc = (() => {
let timer: ReturnType<typeof setTimeout> | null = null;
return () => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
// 防抖函数需要执行的逻辑
}, 1000);
};
})();
方法二:防抖方法,传入一个方法,返回一个具有防抖功能的方法
ts
function debounce<T extends (...args: any[]) => void>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func(...args);
}, delay);
};
}
const log = (message: string) => {
console.log(message);
};
const debouncedLog = debounce(log, 1000);
debouncedLog("Hello");
debouncedLog("World"); // 只有 "World" 会在 1 秒后打印
在实际项目的使用中,我们可以选择lodash库中的debounce,因为我这个项目框架是react,所以也可以使用ahooks中的 useRequest,最后在项目中我选择使用了ahook,(提一句,使用vue框架的同学可以使用ahooks-vue哈),接下来看看具体的代码实现,
tsx
import { useRequest } from "ahooks";
const PageDemo = () => {
const [value, setValue] = useState("");
const requestFunc = async (data: string) => {
// 具体的网络请求代码
};
const { data, run } = useRequest(requestFunc, {
debounceWait: 1000, //设置延迟时间
manual: true, //控制请求是否需要手动触发
});
return (
<Input
onChange={(e) => {
setValue(e.target.value);
run(e.target.value);
}}
/>
);
};
二、通过请求拦截器取消前一次请求
当我们通过这种方式去做的时候,如果不加以任何的限制,就会把效果加在整个项目的请求上,在现在很多的项目中,都是基于axios去封装的请求方法,我们可以加上我们可以写好允许取消的请求地址。接下来直接放代码。
ts
import axios, { AxiosRequestConfig } from "axios";
import { TOKEN } from "@/common/global";
import { getLocal, deleteLocal } from "@/common/utils";
import { message } from "antd";
// 处理 类型"AxiosResponse<any, any>"上不存在属性"errorinfo"。ts(2339) 脑壳疼!关键一步。
declare module "axios" {
interface AxiosResponse<T = any> {
code: any;
msg: any;
data: T;
// 这里追加你的参数
}
}
const cancelableUrls = ["/api/demo", "/api/demo2"];
// 用于存储已经发出了,还没有响应的请求,
const pendingRequest = new Map<string, AbortController>();
const getRequestKey = (config: AxiosRequestConfig): string => {
const { method, url, params, data } = config;
console.log(config);
// 这里的返回,根据项目所需要取消的严格程度
// 例如: 有些项目 只要是相同的url就将上一次的请求取消 ,返回的key只有url组成
// 例如: 有些项目 不仅需要相同的url,并且需要这个请求所携带的参数也相同 返回的key由url和其他的参数组成
// 主要看这里的key的是由那些参数组成的
return `${url}`;
};
// 取消之前的 pending && 重复 的请求
const cancelBeforePendingRequest = (config: AxiosRequestConfig): void => {
const requestKey = getRequestKey(config);
// 如果当前请求的 URL 在 `cancelableUrls` 中
if (cancelableUrls.includes(config.url || "")) {
// 检查是否有未完成的相同请求
if (pendingRequest.has(requestKey)) {
const controller = pendingRequest.get(requestKey);
controller?.abort(); // 取消上一次请求
pendingRequest.delete(requestKey); // 从 Map 中移除
}
// 为当前请求创建一个新的 AbortController
const controller = new AbortController();
config.signal = controller.signal; // 将信号绑定到请求配置
pendingRequest.set(requestKey, controller); // 将当前请求存入 Map
}
};
// 1 创建请求实例,配置基本参数
const http = axios.create({
baseURL: "http://localhost:9528",
});
// 2 添加请求拦截器
http.interceptors.request.use(
function (config: any) {
config.headers = {
"content-Type": "application/json",
...config.headers,
token: getLocal(TOKEN), // 这里可以根据项目需求添加token 也可以不添加
};
config.nonToken && delete config.headers.Token;
cancelBeforePendingRequest(config);
return config;
},
function (error: any) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 3 添加响应拦截器
http.interceptors.response.use(
function (response: any) {
// 请求完成 将pending中的数据删除
const requestKey = getRequestKey(response.config);
pendingRequest.delete(requestKey);
// 对响应数据做点什么
console.log(response, "response");
if (response.data.code === -155) {
deleteLocal(TOKEN);
message.warning({
key: "LOGIN_ERROR",
content: response.data.msg,
});
setTimeout(() => {
window.location.reload();
}, 3000);
}
return response.data;
},
function (error: any) {
if (axios.isCancel(error)) {
console.log("请求被取消", error.message);
} else if (error.name === "AbortError") {
console.log("请求被取消 ( AbortController )");
}
// 对响应错误做点什么
return Promise.reject(error);
}
);
function get<T = any>(url: string, data: any = {}) {
const { ...params } = data;
return http.get<any, { code: number; msg: string; data: T; total?: number }>(
url,
{
params,
}
);
}
function post<T = any>(url: string, params: any = {}, token: string = "") {
const { ...data } = params;
return http.post<any, { code: number; msg: string; data: T }>(url, {
...data,
});
}
/* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */
export default { get, post };
到这儿所有的代码和思路分享就结束啦,最终也是美美的实现了自动保存的功能。