前端项目中遇到的难点

2,418 阅读4分钟

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 使用,那这样避免不了就会出现一些尴尬的事情,比如我们看下图: image.png 没错,就如大家所见,所有的属性都带上了 gettersetter,这意味着,他们都被初始化成了响应式的。由于我们的业务是非常复杂的,所以当我们真的要用一个 config 去描述整个表单时,config 的规模远不止以上这么点,并且整个配置对象的层级可能还会比较深,如果这样的话就可能会有性能问题了。

熟悉 Vue2 的同学都知道,初始化的时候,会对 data 做一个深度遍历添加 getset 变成响应时数据,并且在组件执行 render函数 时,会访问到这些对象的属性。一旦访问到,就会触发 data属性 的依赖收集动作,如果无脑多的属性时,这个 get方法 将被无脑执行。

这肯定不符合我们这种优秀的前端的作风的是吧?怎么搞,优化呗。思路我们也不自己想了,直接拿 尤大 的处理来耍吧哈哈哈。😝

有深入看过 Vue2 源码的同学,对 __ob__ 这个属性一定不陌生,上面截图也有这个属性,但是大家发现没,这个 ob属性 却没有对应的 getset 。让我们打开源码,看看 尤大 做了什么?

首先,在进行响应式处理之前,调用了一个 def 的方法,这里 第四个参数 是没传的

image.png

看看 def 的具体实现,其实就是重新定义这个对象的属性。由于没传 enumerable,所以此时 __ob__的 enumerable 为 false

image.png

没错,我们这里也是采用同样的方式对我们的 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 等数据劫持的方法