WangShuXian6 / blog

FE-BLOG
https://wangshuxian6.github.io/blog/
MIT License
45 stars 10 forks source link

Javascript utils #19

Open WangShuXian6 opened 6 years ago

WangShuXian6 commented 6 years ago

Javascript 工具函数整理

arrayToObject.js https://github.com/redux-utilities/redux-actions/blob/master/src/utils/arrayToObject.js

export default (array, callback) =>
array.reduce(
(partialObject, element) => callback(partialObject, element),
{}
);

camelCase.js https://github.com/redux-utilities/redux-actions/blob/master/src/utils/camelCase.js


import camelCase from 'lodash/camelCase';

const namespacer = '/';

export default type => type.indexOf(namespacer) === -1 ? camelCase(type) : type .split(namespacer) .map(camelCase) .join(namespacer);

<hr>

>compose合并函数依次执行 - 来源redux
>
```javascript
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

数字转为千分位字符

/**
* 数字转为千分位字符
* @param {Number} num
* @param {Number} point 保留几位小数,默认2位
*/
function parseToThousandth(num, point = 2) {
let [sInt, sFloat] = (Number.isInteger(num) ? `${num}` : num.toFixed(point)).split('.')
sInt = sInt.replace(/\d(?=(\d{3})+$)/g, '$&,')
return sFloat ? `${sInt}.${sFloat}` : `${sInt}`
}

带延时功能的链式调用


// 1) 调用方式
new People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian')
// 2) 打印结果
(等待3s)--> 'whr eat apple' -(等待5s)--> 'whr eat durian'
// 3) 以下是代码实现
class People {
constructor(name) {
this.name = name
this.queue = Promise.resolve()
}
eat(food) {
this.queue = this.queue.then(() => {
console.log(`${this.name} eat ${food}`)
})
return this
}
sleep(time = 0) {
this.queue = this.queue.then(() => new Promise(res => {
setTimeout(() => {
res()
}, time)
}))
return this
}
}
<hr>

>一行代码实现简单模版引擎
>
```javascript
function template(tpl, data) {
  return tpl.replace(/{{(.*?)}}/g, (match, key) => data[key.trim()])
}
// 使用:
template('我是{{name}},年龄{{age}},性别{{sex}}', {name: 'aa', age: 18, sex: '男'})
// "我是aa,年龄18,性别男"

循环对象

buildSignString(responseData={page:1,appId:123456}){
let signArr
for (let key in responseData){
// console.log('key--',key) page,appId
// console.log('value--',responseData[key]) 1,123456
}
console.log('signArr---',signArr)
}

<hr/>

>中文url编码
```javascript
newString = encodeURI(string)

去掉字符串首位


const a='abcd'

const b=a.slice(1,-1)

console.log(b) // bc


<hr/>

>将一位数组分割成每三个一组
```javascript
var data = ['法国','澳大利亚','智利','新西兰','西班牙','加拿大','阿根廷','美国','0','国产','波多黎各','英国','比利时','德国','意大利','意大利',];
var result = [];
for(var i=0,len=data.length;i<len;i+=3){
   result.push(data.slice(i,i+3));
}

[['法国','澳大利亚','智利'],['新西兰','西班牙','加拿大'],['阿根廷','美国','0'],['国产','波多黎各','英国'],['比利时','德国','意大利'],['意大利'],]

数组浅拷贝

const newImages = state.list.slice(0)

JS获取URL中参数值(QueryString)

function getQueryString(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配
let context = "";
if (r != null)
context = r[2];
reg = null;
r = null;
return context == null || context == "" || context == "undefined" ? "" : context;
}
alert(getQueryString("q"));

检测是否为 PC 端浏览器


export const isPC = () => { //是否为PC端
const userAgentInfo = navigator.userAgent;
const Agents = ["Android", "iPhone",
"SymbianOS", "Windows Phone",
"iPad", "iPod"];
let flag = true;
for (let v = 0; v < Agents.length; v++) {
if (userAgentInfo.indexOf(Agents[v]) > 0) {
flag = false;
break;
}
}

return flag; }


> 判断是否是在safari浏览器中打开

var issafariBrowser = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);

***
>横竖屏检测最佳实践
```javascript
export const isHorizontalScreen = () => {
  const clientWidth = document.documentElement.clientWidth;
  const screenWidth = window.screen.width;
  const screenHeight = window.screen.height;
  // 2.在某些机型(如华为P9)下出现 srceen.width/height 值交换,所以进行大小值比较判断
  const realScreenWidth = screenWidth < screenHeight ? screenWidth : screenHeight;
  const realScreenHeight = screenWidth >= screenHeight ? screenWidth : screenHeight;
  if (clientWidth == realScreenWidth) {
    // 竖屏
    return false
  }
  if (clientWidth == realScreenHeight) {
    // 横屏
    return true
  }
}

image to dataurl


export function getDataUri(url, callback) {
let image = new Image();
image.onload = function () {
    let canvas = document.createElement('canvas');
    canvas.width = image.width; // or 'width' if you want a special/scaled size
    canvas.height = image.height; // or 'height' if you want a special/scaled size

    canvas.getContext('2d').drawImage(image, 0, 0);

    // Get raw image data
    //callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));

    // ... or get as Data URI
    callback(canvas.toDataURL('image/png'));
};

image.src = url;

}

// Usage getDataUri('/logo.png', function (dataUri) { // Do whatever you'd like with the Data URI! });


***

>计时

>console.time()和console.timeEnd()方法
>Chrome等浏览器自带一个console.time()和console.timeEnd()方法,能够用更简单的代码实现上述功能。
>当需要统计一段代码的执行时间时,可以使用console.time方法与console.timeEnd方法,其中console.time方法用于标记开始时间,console.timeEnd方法用于标记结束时间,并且将结束时间与开始时间之间经过的毫秒数在控制台中输出。这两个方法的使用方法如下所示。
console.time(label)
console.timeEnd(label)
这两个方法均使用一个参数,参数值可以为任何字符串,但是这两个方法所使用的参数字符串必须相同,才能正确地统计出开始时间与结束时间之间所经过的毫秒数。

```ts
console.time(label)
console.timeEnd(label)

let start = window.performance.now();
...
let end = window.performance.now();
let time = end - start;

去掉当前页面的 hash 而不刷新页面


window.history.pushState(
{},
'',
window.location.href.slice(
0,
window.location.href.indexOf('#')
? window.location.href.indexOf('#')
: 0))
***
>获取前N天的日期

>方法一: 用setDate();
```ts
function getDate(index){
    let date = new Date(); //当前日期
    let newDate = new Date();
    newDate.setDate(date.getDate() + index);//官方文档上虽然说setDate参数是1-31,其实是可以设置负数的
    let time = newDate.getFullYear()+"-"+(newDate.getMonth()+1)+"-"+newDate.getDate();
    return time;
}
console.log(getDate(7)); // 2019-7-10
console.log(getDate(-7)); // 2019-6-26

方法二: 时间戳进行转换

let date= new Date();
let newDate = new Date(date.getTime() - 7*24*60*60*1000);
let time = newDate.getFullYear()+"-"+(newDate.getMonth()+1)+"-"+newDate.getDate();
conosole.log(time);

// 获取近n天的日期列表


function getDateList(index) {
let list = []
if (index >= 0) {
for (let i = 0; i < index; i++) {
list.push(getDate(i))
}
} else {
for (let i = 0; i > index; i--) {
list.push(getDate(i))
}
list.reverse()
}
return list
}

console.log(getDateList(-2)) // ["2019-7-2", "2019-7-3"]

>复制内容到剪贴板
```ts
function copy(content) {
    const input = document.createElement('input');
    input.setAttribute('readonly', 'readonly');
    input.setAttribute('value', content);
    document.body.appendChild(input);
    input.setSelectionRange(0, 9999);
    input.select()
    if (document.execCommand('copy')) {
      const result = document.execCommand('copy');
      if (result) {
        console.log('复制成功');
      } else {
        console.warn('复制失败')
      }

    }
    document.body.removeChild(input);
  }

URLSearchParams 查询参数

假设浏览器的url参数是 "?name=蜘蛛侠&age=16"

new URLSearchParams(location.search).get("name"); // 蜘蛛侠

classList

这是一个对象,该对象里封装了许多操作元素类名的方法:

<p class="title"></p>

let elem = document.querySelector("p");

// 增加类名
elem.classList.add("title-new"); // "title title-new"

// 删除类名
elem.classList.remove("title"); // "title-new"

// 切换类名(有则删、无则增,常用于一些切换操作,如显示/隐藏)
elem.classList.toggle("title"); // "title-new title"

// 替换类名
elem.classList.replace("title", "title-old"); // "title-new title-old"

// 是否包含指定类名
elem.classList.contains("title"); // false

online state

监听当前的网络状态变动,然后执行对应的方法:

window.addEventListener("online", xxx);

window.addEventListener("offline", () => {
  alert("你断网啦!");
});
  1. deviceOrientation

陀螺仪,也就是设备的方向,又名重力感应,该API在IOS设备上失效的解决办法,将域名协议改成https;

从左到右分别为alpha、beta、gamma;

window.addEventListener("deviceorientation", event => {
  let {
    alpha,
    beta,
    gamma
  } = event;

  console.log(`alpha:${alpha}`);
  console.log(`beta:${beta}`);
  console.log(`gamma:${gamma}`);
});

使用场景:页面上的某些元素需要根据手机摆动进行移动,达到视差的效果,比如王者荣耀进入游戏的那个界面,手机转动背景图会跟着动😂

orientation

可以监听用户手机设备的旋转方向变化;

 window.addEventListener("orientationchange", () => {
  document.body.innerHTML += `<p>屏幕旋转后的角度值:${window.orientation}</p>`;
}, false);

也可以使用css的媒体查询:

/* 竖屏时样式 */
@media all and (orientation: portrait) {
  body::after {
    content: "竖屏"
  }
}

/* 横屏时样式 */
@media all and (orientation: landscape) {
  body::after {
    content: "横屏"
  }
}

使用场景:页面需要用户开启横屏来获得更好的体验,如王者荣耀里面的活动页😂

WangShuXian6 commented 6 years ago

加密库

https://www.npmjs.com/package/crypto-js npm i --save crypto-js


import MD5 from 'crypto-js/md5'
import sha256 from 'crypto-js/sha256'

let stringWithMd5=MD5(stringWithKey).toString().toUpperCase()

WangShuXian6 commented 6 years ago

时间

new Date()
//Sat Apr 28 2018 14:59:17 GMT+0800 (CST)
+new Date()
//1524898762153

Miment https://github.com/noahlam/Miment/blob/master/README-cn.md

npm i miment
import miment from 'miment'
miment().format('YYYY/MM/DD hh-mm-ss SSS') // 2018/04/09 23-49-36 568

字符串格式数字转换为数值类型


typeof(1)
"number"

typeof('1') "string"

typeof(+'1') "number"

>注意
```javascript
console.log(+'a1')
NaN

console.log(+'1a')
NaN

console.log(+'1')
1

匹配,替换,过滤出


var curry = require('lodash').curry;

var match = curry(function(what, str) { return str.match(what); });

var replace = curry(function(what, replacement, str) { return str.replace(what, replacement); });

var filter = curry(function(f, ary) { return ary.filter(f); });

var map = curry(function(f, ary) { return ary.map(f); });


```javascript
match(/\s+/g, "hello world");
// [ ' ' ]

match(/\s+/g)("hello world");
// [ ' ' ]

var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }

hasSpaces("hello world");
// [ ' ' ]

hasSpaces("spaceless");
// null

filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]

var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }

findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]

var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }

var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }

censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'

reverse 反转列表,head 取列表中的第一个元素;所以结果就是得到了一个 last 函数(即取列表的最后一个元素),虽然它性能不高。这个组合中函数的执行顺序应该是显而易见的。尽管我们可以定义一个从左向右的版本,但是从右向左执行更加能够反映数学上的含义

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
}
var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head, reverse);

last(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'uppercut'
var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));

initials("hunter stockton thompson");
// 'H. S. T'

HTML实体编码


// HTML实体编码

function escapeHtml(string) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' } return String(string).replace(/[&<>"'\/]/g, function (s) { return entityMap[s] }) }

var string5 = "

test2222
"


<hr>

>使用展开运算符后的数组对象
```javascript
const arr = [...Array(100)].map((_, i) => i);
console.log(arr[0]);

循环对象

for (let optionItem in this.options){
console.log('toggleOptions---optionItem',optionItem)
}

日历


const caleandar={
month: new Date().getMonth()+1, // 本月月份
date: new Date().getDate(), // 今日日期
day: new Date().getDay(), // 今日周几
year: new Date().getFullYear(), // 年份
}

var mydate=new Date(); var myyear=mydate.getYear(); var mymonth=mydate.getMonth()+1 //注:月数从0~11为一月到十二月 var mydat=mydate.getDate() var myhours=mydate.getHours() var myminutes=mydate.getMinutes() var myseconds=mydate.getSeconds() var myday=mydate.getDay() //注:0-6对应为星期日到星期六


```javascript
// 获取本月1日周几
      const day=new Date(this.caleandar.year,this.caleandar.month-1,1).getDay()
// 获得某月天数
    getDaysInOneMonth(year, month) {
      month = parseInt(month, 10)
      var d = new Date(year, month, 0)
      return d.getDate()
    }
// 计算该月日期集合
    getDaysArray(day,days) {
      console.log(day,'',days)
      let daysArray=[]
      let dayNumber=1

      while (day){
        daysArray.push('')
        day--
      }

      while (days){
        daysArray.push(dayNumber)
        dayNumber++
        days--
      }

      return daysArray
    }

格式化时间戳

/**
* 格式化时间戳
*
* 2018/8/1 下午3:21:02
*
* @param timestamp
* @returns {string}
*/
export const formatTimestamp = (timestamp) => {
let unixTimestamp = new Date(timestamp * 1000)
return unixTimestamp.toLocaleString()
}

解析url参数


/**
* 解析url参数
* @param url
* @returns {{}}
*/
parseURL(url) {
let qs = url.split("?")
qs = qs[1] ? qs[1] : ""
let obj = {}
if ('string' !== typeof qs || qs.length === 0) {
return obj
}
let key = []
let eq = '='
let decode = decodeURIComponent
qs = qs.split("&")
let qsLen = qs.length
for (let i = 0; i < qsLen; ++i) {
  let x = qs[i]
  let idx = x.indexOf('=')
  let k
  let v
  if (idx >= 0) {
    k = decode(x.substring(0, idx))
    v = decode(x.substring(idx + 1))
  } else {
    k = x
    v = ''
  }
  if (key.indexOf(k) === -1) {
    obj[k] = v
    key.push(k)
  } else if (obj[k] instanceof Array) {
    obj[k].push(v)
  } else {
    obj[k] = [obj[k], v]
  }
}

return obj

}

<hr>

>获取格林尼治时间
```ts
new Date().toGMTString()
var dt = new Date;
dt.setMinutes( dt.getMinutes() + dt.getTimezoneOffset() ); // 当前时间(分钟) + 时区偏移(分钟)
console.log( "格林尼治时间戳: ", dt.getTime() );
console.log( "用本地时间格式显示: ", dt.toLocaleString() );

JSON 转 formData

function buildFormParams(params) {
let formData = new FormData();
for (let name in params) {
formData.append(`${name}`, `${params[name]}`);
}
return formData;
}

fetch


const paramsStr = buildFormParams({});
    fetch(CouponsApi, {
      method: 'POST',
      body: paramsStr,
      headers: {},
    })
      .then((res) => {
        res.text().then((responseText) => {
          const response = JSON.parse(responseText);
          console.log('response', response)
          const code = response.user_code || ''
        });
      })
      .catch((error) => {
        console.warn('error', error)
      });
WangShuXian6 commented 6 years ago

校验

手机号格式验证

isMoblie(phone) {
return /^1(3|5|6|7|8|9)[0-9]{9}$/.test(phone)
}

WangShuXian6 commented 6 years ago

匹配

将p标签转换为view标签


const brTagReg = /<br>|<br\/>/gi
const pLeftTagReg = /<p>/gi
const pRightTagReg = /<\/p>/gi

export const fromatPTag = (htmlContent) => { let string = htmlContent.replace(brTagReg, '') string = string.replace(pLeftTagReg, '') return string.replace(pRightTagReg, '') }

const a = '

test

111
222
' console.log(fromatPTag(a))

WangShuXian6 commented 6 years ago

移动相关

滚动到页面指定元素 https://gist.github.com/WangShuXian6/e9e37ba8e2a540fcc5b1af7d69966c60 http://jsbin.com/sakayax/1/edit?html,output


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>滚动到页面指定元素</title>
<style type="text/css">
#target{
position: absolute;
top:2000px;
width: 100%;
background: #49a9ee;
}
#btn{
position: fixed;
top:0;
left:0;
padding:5px 20px;
background: #00a854;
}
</style>
</head>
<body>
<button type="button" id="btn">scroll</button>
<div id="target">Hello world</div>
<script>
let btn=document.getElementById('btn');
let target=document.getElementById('target');

function animateScroll(element,speed) { let rect=element.getBoundingClientRect(); //获取元素相对窗口的top值,此处应加上窗口本身的偏移 let top=window.pageYOffset+rect.top; let currentTop=0; let requestId; //采用requestAnimationFrame,平滑动画 function step(timestamp) { currentTop+=speed; if(currentTop<=top){ window.scrollTo(0,currentTop); requestId=window.requestAnimationFrame(step); }else{ window.cancelAnimationFrame(requestId); } } window.requestAnimationFrame(step); }

btn.onclick=function (e) { animateScroll(target,50); };


<hr/>

>滚动到顶部
```javascript
initScrollEvent()

      function initScrollEvent() {
        const goTopButton = document.getElementsByClassName('go-top')[0]
        goTopButton.onclick = handleGoTop
      }

      function scrollToTop() {
        if (document.body.scrollTop != 0 || document.documentElement.scrollTop != 0) {
          window.scrollBy(0, -50);
          timeOut = setTimeout('scrollToTop()', 10);
        }
        else clearTimeout(timeOut);
      }

      function handleGoTop(e) {
        e.preventDefault()
        scrollToTop()
        console.log('ee')
      }
WangShuXian6 commented 6 years ago

DOM

查找元素

let doms=document.querySelectorAll("a[title='jump-to-ten']")
WangShuXian6 commented 5 years ago

网络请求

<!-- ih_request.js -->
const request = require('request');
var ih_request = {};
module.exports = ih_request;
ih_request.get = async function(option){
var res = await req({
url: option.url,
method: 'get'
})
res.result?option.success(res.msg):option.error(res.msg);
}
const request = require('../script/ih_request');
await request.get({
url: 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET',
success: function(res){
console.log(res.access_token)
},
error: function(err){
console.log(err)
}
})

WangShuXian6 commented 5 years ago

最佳实践

如果有更好的实现,尽量不要使用三元表达式

let score = val ? val : 0     // ✗ 错误
let score = val || 0          // ✓ 正确

add remove

const tabUtil= {
        addTab: id => {
          this.setState({
            tabs: [...tabs, id],
          });
        },
        removeTab: id => {
          this.setState({
            tabs: tabs.filter(currentId => currentId !== id),
          });
        },
      },
WangShuXian6 commented 5 years ago

使用字典格式重构多条件判断语句

export const DIRECTION = {
  LEFT: 'LEFT',
  RIGHT: 'RIGHT',
  TOP: 'TOP',
  BOTTOM: 'BOTTOM',
  HORIZONTAL: 'HORIZONTAL',
  VERTICAL: 'VERTICAL'
}

export const ARROW_DIRECTION = [
  {isHorizontalScreen: true, isScrollVertical: false, direction: DIRECTION.HORIZONTAL},
  {isHorizontalScreen: false, isScrollVertical: false, direction: DIRECTION.VERTICAL},
  {isHorizontalScreen: false, isScrollVertical: true, direction: DIRECTION.HORIZONTAL},
  {isHorizontalScreen: true, isScrollVertical: true, direction: DIRECTION.VERTICAL},
]

const arrowDirectionData = ARROW_DIRECTION.filter((item) => {
      return item.isScrollVertical === this.config.canvas.isScrollVertical &&
        item.isHorizontalScreen === isHorizontalScreen()
    })
    const renderType = arrowDirectionData.length ? arrowDirectionData[0].direction : 'undefind'
WangShuXian6 commented 5 years ago
const html=(strings,...values)=>{
      return values.reduce(
        (s,v,i)=>s+String(v)+strings[i+1],strings[0]
      )
    }
WangShuXian6 commented 2 years ago

格式化

将对象属性所有值转为字符串

function eventToStringAdapter(event={}){
  if(typeof event !== 'object'){
    return console.warn('事件不是对象:',event)
  }
  const keys=Object.keys(event)
  const stringEvent=keys.reduce((accumulator, currentKey)=>{
    const currentPram=event[currentKey]
    if(typeof currentPram === 'object'){
      const stringPram={[currentKey]:eventToStringAdapter(currentPram)}
      return Object.assign({},accumulator,stringPram)
    }
    const stringPram={[currentKey]:String(currentPram)}
    return Object.assign({},accumulator,stringPram)
  },{})
  return stringEvent
}

const demoAAA={
  a:1,
  b:{
    A:2,
    B:'3',
    C:true,
    D:false,
    E:0
  },
  c:{
    AA:2,
    BB:{
      EE:33,
      DD:false
    }
  }
}

const demoBBBB=eventToStringAdapter(demoAAA)
WangShuXian6 commented 2 weeks ago

将对象的属性的字符串类型值 'null' 替换为 null

    // 将 'null' 字符串替换为 null
    const sanitizedMessage = Object.fromEntries(
      Object.entries(message).map(([key, value]) => [key, value === 'null' ? null : value])
    ) as typeof message
WangShuXian6 commented 3 days ago

fetch 请求工具

fetch 防抖

type FetchOptions = RequestInit & { signal?: AbortSignal }

const pendingFetchRequests: Record<string, { controller: AbortController }> = {}

export const debounceFetch = async (url: string, options: FetchOptions = {}) => {
  const requestKey = `${url}_${JSON.stringify(options)}`

  if (pendingFetchRequests[requestKey]) {
    // 如果存在相同的请求,取消当前请求
    pendingFetchRequests[requestKey].controller.abort()
  }

  // 创建一个新的控制器,并存储请求信息
  const controller = new AbortController()
  pendingFetchRequests[requestKey] = { controller }

  options.signal = controller.signal

  try {
    const response = await fetch(url, options)
    // 请求成功后,删除存储的防抖信息
    delete pendingFetchRequests[requestKey]
    return response
  } catch (error) {
    if (error instanceof DOMException && error.name === 'AbortError') {
      console.log('请求被取消:', error.message)
    }
    throw error
  }
}

示例

import { debounceFetch } from './debounceFetch'

const options = {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  }
  // body: JSON.stringify({ ticket }) // 如果需要请求体,可以取消注释
}

try {
  const response = await debounceFetch('http://api', options)
  if (response.ok) {
    const data = await response.json()
    console.log('请求成功:', data)
  } else {
    console.error('请求失败:', response.status)
  }
} catch (error) {
  if (error instanceof TypeError) {
    console.error('网络请求失败或其他网络问题:', error.message)
  } else if (error instanceof DOMException && error.name === 'AbortError') {
    console.error('请求被取消:', error.message)
  } else {
    console.error('其他错误:', error)
  }
}
WangShuXian6 commented 3 days ago

axios 请求工具

axios 简单版

import axios from 'axios'

const baseURL = import.meta.env.VITE_BASE_URL
const axiosInstance = axios.create({
  baseURL,
  timeout: 5000
})

axiosInstance.interceptors.request.use(
  (config) => {
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

axiosInstance.interceptors.response.use(
  (response) => {
    return response
  },
  (error) => {
    return Promise.reject(error)
  }
)

export default axiosInstance

axios 防抖

import axios from 'axios'

const baseURL = import.meta.env.VITE_BASE_URL
const axiosInstance = axios.create({
  baseURL,
  timeout: 5000
})

// 防抖请求存储
const pendingRequests: Record<string, { cancel: (message: string) => void }> = {}

axiosInstance.interceptors.request.use(
  (config) => {
    const requestKey = `${config.url}_${JSON.stringify(config.params)}_${JSON.stringify(
      config.data
    )}`
    if (pendingRequests[requestKey]) {
      // 如果存在相同的请求,取消当前请求
      pendingRequests[requestKey].cancel('请求被防抖拦截')
    }
    // 创建一个新的取消令牌,并存储请求信息
    config.cancelToken = new axios.CancelToken((cancel) => {
      pendingRequests[requestKey] = { cancel }
    })
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

axiosInstance.interceptors.response.use(
  (response) => {
    // 请求成功后,删除存储的防抖信息
    const requestKey = `${response.config.url}_${JSON.stringify(
      response.config.params
    )}_${JSON.stringify(response.config.data)}`
    delete pendingRequests[requestKey]
    return response
  },
  (error) => {
    if (axios.isCancel(error)) {
      console.log('请求被取消:', error.message)
    }
    return Promise.reject(error)
  }
)

export default axiosInstance

示例

// example.ts
import request from './httpService'

const fetchData = async () => {
  try {
    // GET 请求示例
    const getResponse = await request({
      url: '/path/to/resource',
      params: { id: 1 }
    })
    console.log(getResponse)

    // POST 请求示例
    const postResponse = await request({
      method: 'post',
      url: '/path/to/resource',
      data: { key: 'value' }
    })
    console.log(postResponse)
  } catch (error) {
    console.error(error)
  }
}

// 调用函数获取数据
fetchData()

axios 全局拦截器

// 设置 Axios 拦截器
import { useEffect } from 'react'
import axios from 'axios'
import { useAuth } from './auth'

export const useAxiosInterceptor = () => {
  const { accessToken, refreshAccessToken } = useAuth()
  let isRefreshing = false
  let requestQueue: ((newToken: string) => void)[] = []

  useEffect(() => {
    const requestInterceptor = axios.interceptors.request.use((config) => {
      if (accessToken) {
        config.headers['Authorization'] = `Bearer ${accessToken}`
      }
      return config
    })

    const responseInterceptor = axios.interceptors.response.use(
      async (response) => {
        if (response.data.code === 401 && !isRefreshing) {
          isRefreshing = true
          try {
            await refreshAccessToken()
            requestQueue.forEach((cb) => cb(accessToken || ''))
            requestQueue = []
          } catch (err) {
            requestQueue = []
            throw err
          } finally {
            isRefreshing = false
          }
        } else if (response.status === 401 && isRefreshing) {
          return new Promise((resolve) => {
            requestQueue.push((newToken: string) => {
              response.config.headers['Authorization'] = `Bearer ${newToken}`
              resolve(axios(response.config))
            })
          })
        }
        return response
      },
      async (error) => {
        if (error.response.status === 401 && !isRefreshing) {
          isRefreshing = true
          try {
            await refreshAccessToken()
            requestQueue.forEach((cb) => cb(accessToken || ''))
            requestQueue = []
          } catch (err) {
            requestQueue = []
            throw err
          } finally {
            isRefreshing = false
          }
        } else if (error.response.status === 401 && isRefreshing) {
          return new Promise((resolve) => {
            requestQueue.push((newToken: string) => {
              error.config.headers['Authorization'] = `Bearer ${newToken}`
              resolve(axios(error.config))
            })
          })
        }
        return Promise.reject(error)
      }
    )

    return () => {
      axios.interceptors.request.eject(requestInterceptor)
      axios.interceptors.response.eject(responseInterceptor)
    }
  }, [accessToken])
}