1. axios中断请求
为什么需要请求中断
- 多次发送同一请求:会造成堵塞,应该在每次发送请求时中断前面的请求
- 多次发送不同请求:前面的请求还没响应,又接着发送另外的请求,前面的接口响应数据会与当前接口混淆。
- 项目路由切换的时候,终止之前页面的请求。
2. 中断请求两种方式
2.1 CancelToken构造函数生成cancel函数
axios内置CancelToken类,并且new时可以传入回调函数,回调函数接受一个参数cancel函数,CancelToken会把取消回调注入给参数callback,外部使用cancelCallback接收。
data:{
return {
cancelCallback: null //取消请求
}
}
async searchCoupon() {
try {
// 重复请求时直接取消之前的请求
if (typeof this.cancelCallback === 'function') {
this.cancelCallback('Operation canceled by the user.')
this.cancelCallback = null
}
let [err, res] = await searchCoupon({
data: this.searchCouponParam,
cancelToken: new this.$axios.CancelToken(callback => (this.cancelCallback = callback))
})
if (err) throw err
this.bestCouponList = (res && res.list) || []
} catch (error) {
console.log(error)
}
},
3.2 CancelToken.source()生成取消令牌token
data:{
return {
cancelTokenSource: null //取消请求
}
}
// 优惠券检索
async couponSearch(val) {
//如果存在CancelTokenSource,手动取消请求
if (this.cancelTokenSource) {
this.cancelTokenSource.cancel('cancel by hand')
this.cancelTokenSource = null
}
const cancelToken = this.$axios.CancelToken
this.cancelTokenSource = cancelToken.source()
try {
let [err, res] = await couponSearch({
cancelToken: this.cancelTokenSource.token,
data: {
sku_ids: this.skuIds || '',
keyword: this.useCode || ''
}
})
this.searchCouponList = res.list || []
} catch (error) {
console.error(error)
}
}
3.结合watchEffect清除副作用使用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (场景:有一个页码组件里面有5个页码,点击就会异步请求数据。于是做一个监听,监听当前页码,只要有变化就请求一次。问题:如果点击的比较快,从1到5全点了一遍,那么会有5个请求,最终页面会显示第几页的内容?第5页?那是假定请求第5页的ajax响应的最晚,事实呢?并不一定。于是这就会导致错乱。还有一个问题,连续快速点5次页码,等于我并不想看前4页的内容,那么是不是前4次的请求都属于带宽浪费?这也不好。
于是官方就给出了一种解决办法: 侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。 当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时;
- 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
<template>
<div>
<div>content: {{ content }}</div>
<button @click="changePageNumber">第{{ pageNumber }}页</button>
</div>
</template>
<script>
import axios from 'axios';
import { ref, watchEffect } from 'vue';
export default {
setup() {
let pageNumber = ref(1);
let content = ref('');
const changePageNumber = () => {
pageNumber.value++;
}
watchEffect((onInvalidate) => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
onInvalidate(() => {
source.cancel();
});
axios.get(`http://localhost:3000/${pageNumber.value}.json`, {
cancelToken: source.token,
}).then((response) => {
content.value = response.data.content;
}).catch(function (err) {
if (axios.isCancel(err)) {
console.log('Request canceled', err.message);
}
});
});
return {
pageNumber,
content,
changePageNumber,
};
},
};
</script>
4.Vue2属性配置静态化
我们上述实现配置化的时候,直接把整个 config 赋值给 data ,然后在 App组件 的 el-form 中 v-for 使用,那这样避免不了就会出现一些尴尬的事情,比如我们看下图:
没错,就如大家所见,所有的属性都带上了
getter 和 setter,这意味着,他们都被初始化成了响应式的。由于我们的业务是非常复杂的,所以当我们真的要用一个 config 去描述整个表单时,config 的规模远不止以上这么点,并且整个配置对象的层级可能还会比较深,如果这样的话就可能会有性能问题了。
熟悉 Vue2 的同学都知道,初始化的时候,会对 data 做一个深度遍历添加 get 、 set 变成响应时数据,并且在组件执行 render函数 时,会访问到这些对象的属性。一旦访问到,就会触发 data属性 的依赖收集动作,如果无脑多的属性时,这个 get方法 将被无脑执行。
这肯定不符合我们这种优秀的前端的作风的是吧?怎么搞,优化呗。思路我们也不自己想了,直接拿 尤大 的处理来耍吧哈哈哈。😝
有深入看过 Vue2 源码的同学,对 __ob__ 这个属性一定不陌生,上面截图也有这个属性,但是大家发现没,这个 ob属性 却没有对应的 get 、set 。让我们打开源码,看看 尤大 做了什么?
首先,在进行响应式处理之前,调用了一个 def 的方法,这里 第四个参数 是没传的
看看 def 的具体实现,其实就是重新定义这个对象的属性。由于没传 enumerable,所以此时 __ob__的 enumerable 为 false
没错,我们这里也是采用同样的方式对我们的 config 进行 非响应式 优化。其实整个 config 数据,我们只是需要保证 value 是响应式的即可,其他很多描述性数据都是大可不必的。那我们就把其他字段进行一个优化~
// 优化函数
function optimize (array) {
return array.reduce((acc, cur) => {
for (const key of Object.keys(cur)) {
if (key === 'value') continue
// 将不是 value 的属性都进行非响应式优化
Object.defineProperty(cur, [key], { enumerable: false })
}
acc.push(cur)
return acc
}, [])
}
Object.freeze()
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
const obj = {
prop: 42
};
Object.freeze(obj);
obj.prop = 33;
// Throws an error in strict mode
console.log(obj.prop);
// expected output: 42
Vue 在遇到像 Object.freeze() 这样被设置为不可配置之后的对象属性时,不会为对象加上 setter getter 等数据劫持的方法