lgwebdream/FE-Interview

Day103:多个 tab 只对应一个内容框,点击每个 tab 都会请求接口并渲染到内容框,怎么确保频繁点击 tab 但能够确保数据正常显示?

Genzhen opened this issue · 2 comments

每日一题会在下午四点在交流群集中讨论,五点 Github、交流群同步更新答案

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁800+道前端面试题。

每日一题会在下午四点在交流群集中讨论,五点 Github、交流群同步更新答案

一、分析

因为每个请求处理时长不一致,可能会导致先发送的请求后响应,即请求响应顺序和请求发送顺序不一致,从而导致数据显示不正确。

即可以理解为连续触发多个请求,如何保证请求响应顺序和请求发送顺序一致。对于问题所在场景,用户只关心最后数据是否显示正确,即可以简化为:连续触发多个请求,如何保证最后响应的结果是最后发送的请求(不关注之前的请求是否发送或者响应成功)

类似场景:input输入框即时搜索,表格快速切换页码

二、解决方案

防抖(过滤掉一些非必要的请求) + 取消上次未完成的请求(保证最后一次请求的响应顺序)

取消请求方法:

  • XMLHttpRequest 使用 abort api 取消请求
  • axios 使用 cancel token 取消请求

伪代码(以 setTimeout 模拟请求,clearTimeout 取消请求)

/**
 * 函数防抖,一定时间内连续触发事件只执行一次
 * @param {*} func 需要防抖的函数
 * @param {*} delay 防抖延迟
 * @param {*} immediate 是否立即执行,为true表示连续触发时立即执行,即执行第一次,为false表示连续触发后delay ms后执行一次
 */
let debounce = function(func, delay = 100, immediate = false) {
  let timeoutId, last, context, args, result

  function later() {
    const interval = Date.now() - last
    if (interval < delay && interval >= 0) {
      timeoutId = setTimeout(later, delay - interval)
    } else {
      timeoutId = null
      if (!immediate) {
        result = func.apply(context, args)
        context = args = null
      }
    }
  }

  return function() {
    context = this
    args = arguments
    last = Date.now()

    if (immediate && !timeoutId) {
      result = func.apply(context, args)
      context = args = null // 解除引用
    }
    
    if (!timeoutId) {
      timeoutId = setTimeout(later, delay)
    }

    return result
  }
}


let flag = false   // 标志位,表示当前是否正在请求数据
let xhr = null

let request = (i) => {
    if (flag) {
        clearTimeout(xhr)
        console.log(`取消第${i - 1}次请求`)
    }
    flag = true
    console.log(`开始第${i}次请求`)
    xhr = setTimeout(() => {
        console.log(`请求${i}响应成功`)
        flag = false
    }, Math.random() * 200)
}

let fetchData = debounce(request, 50)  // 防抖

// 模拟连续触发的请求
let count = 1 
let getData = () => {
  setTimeout(() => {
    fetchData(count)
    count++
    if (count < 11) {
        getData()
    }
  }, Math.random() * 200)
}
getData()

/* 某次测试输出:
    开始第2次请求
    请求2响应成功
    开始第3次请求
    取消第3次请求
    开始第4次请求
    请求4响应成功
    开始第5次请求
    请求5响应成功
    开始第8次请求
    取消第8次请求
    开始第9次请求
    请求9响应成功
    开始第10次请求
    请求10响应成功
*/

确保频繁点击tab能够正常显示数据的关键是避免过多不必要的网络请求和渲染。

以下是一些可能有用的建议:

1.缓存结果:每次请求数据时,可以将结果存储在本地缓存中,这样就不需要每次都向服务器请求数据,并且可以快速渲染已经获得的数据。

2.减少请求:可以在用户点击tab时添加一个延迟器(debouncer),这个延迟器可以在用户点击tab后暂停一小段时间(如 200ms),以防止用户频繁点击产生太多的请求。

3.管理请求:可以使用RxJS这样的库来管理请求。RxJS使得请求管理更加方便,以便用户无需过多考虑每个请求的完成时间。通过使用RxJS,可以避免重复请求并保存最新的请求结果。

4.优化渲染:可以使用虚拟滚动(virtual scrolling)来优化渲染。虚拟滚动可以只渲染用户实际所需的内容,而不是渲染整个列表。

针对复杂业务建模:对于更复杂的业务,可以使用状态机来管理状态。状态机可以在不同的状态之间进行切换,并且每个状态只有一组特定的操作序列。例如,当一个 tab 被点击时,可以将状态机从“等待”状态转换为“请求”状态,然后再转换到“成功”或“失败”状态。

总之,为了确保频繁点击tab时数据正常显示,需要综合运用以上建议,例如使用本地缓存、减少请求,使用RxJS来管理请求等方式来进行优化,以实现更流畅的用户体验。