【JS】【React】多源表格数据的异步渲染

基本情况:在React项目中一个表格的数据是由A接口和B接口这两个接口的数据拼合而成,且其中表格列colD的数据是从B接口获取的,而B接口的调用需要拿A接口中获取的每一条数据的id进行请求。

方法一:

一般遇到这种问题最先想到的就是先调用A接口,再遍历A接口的数据一条一条的循环请求B接口,将数据拼在一起后再渲染表格数据。

// 表格数据
const [anDataSource, setDataSource] = useState([]);

// 获取表格数据函数
const doSearch = async () => {
	// 分页查询的参数
	const params = {
		pageNum: 1,
		pagesize: 10,
		... //其它请求参数
	}
	// 先调用A接口
	const tempData = await getA(params);
	// 接口返回的数据格式大致如下:
	/* 
		{
			code: "0",
			list: [
				{
					id: 1,
					colA: '数据1',
					colB: '数据1',
					colC: '数据1',
				},
				{
					id: 2,
					colA: '数据2',
					colB: '数据2',
					colC: '数据2',
				},
				...
			],
			total: 10,
			success: true
		}
	*/
	const promiseAll = tempData.list?.map((item) => {
    	return getB({ id: item.id }).then(({ data }) => ({ ...item, colD: data }));
     });
    const resultData = await Promise.allSettled(promiseAll);
    tempData.list = resultData.map((item) => item.value);
    // 获取最终的表格数据
    setDataSource(tempData.list);
}

<Table dataSource={anDataSource} hasBorder={false} primaryKey="id" ></Table>

方法一中每次查询数据都需要循环调用接口,等所有接口数据返回后再渲染表格,这在一定程度上加长了表格loading的显示时长,对于用户的体验感不是很好。并且当用户短时间内连续多次点击“下一页”按钮时有多个请求处于pending待响应状态,容易出现接口请求超时的情况。

方法二

可以先渲染从A接口拿到的数据,等用户在此页面停留3秒后再调用B接口,等全部接口请求完成后重新渲染表格数据。
我们可以设置一个isSearchFlag用于标识是否处于请求状态,并用currentTableData存储当前表格数据。

const {
    currentTableData,
} = state;

// 获取表格数据函数
const doSearch = async () => {
	isSearchFlag = false;
	const params = {
		pageNum: 1,
		pagesize: 10,
		... //其它请求参数
	}
	const tempData = await getA(params);
    setState({
      currentTableData: tempData.list
    });
    isSearchFlag = true;
}

useEffect(() => {
    if (isSearchFlag) {
      setTimeout(() => {
        console.log('Searching...');
        async function fetchData() {
        const promiseAll = currentTableData?.map((item) => {
	    	return getB({ id: item.id }).then(({ data }) => ({ ...item, colD: data }));
	     });
	    const resultData = await Promise.allSettled(promiseAll);
        let tempArray = currentTableData;
        tempArray = resultData.map((item) => item.value);
        setState({
          currentTableData: tempArray
        });
      }
      fetchData();
     }, 3000); // 3秒
   }
 }, [isSearchFlag]);

<Table dataSource={currentTableData} hasBorder={false} primaryKey="id" ></Table>

方法三

在路由拦截器中设置请求计数器requestCountMap,设置接口请求次数限制为20,当请求区的计数达到限制次数时拦截此后的请求,只有当有请求被响应后再允许发起新的请求。

const request = axios.create({
  baseURL: baseURL,
  // 可选的,全局设置 request 是否返回 response 对象,默认为 false
  // withCredentials: true,
});

// 存储请求计数的Map
const requestCountMap = new Map();

// 添加请求拦截器
request.interceptors.request.use(config=>{
	// config 请求配置
	if (config.url.indexOf('B接口地址') > -1) {
      // 获取当前URL的计数
      let count = requestCountMap.get(config.url) || 0;
      // 如果计数超过20,抛出错误阻止请求
      if (count > 20) {
        throw new Error('Request limit exceeded.');
      }
      // 更新计数
      requestCountMap.set(config.url, ++count);
    }
	// 可用于
	// 发送网络请求时,在界面显示一个请求的同步动画
	// 某些请求(比如登录(token))必须携带一些特殊的信息
	
	// 请求成功拦截
    console.log("请求拦截器")
    return config
}, err => {
	// 请求失败拦截
    return Promise.reject(err)
})  

// 添加响应拦截器
request.interceptors.response.use(res=>{
	// res 响应结果
	// 响应拦成功拦截
	if (response.config.url.indexOf('B接口地址') > -1) {
      // 请求成功处理
      const config = response.config;
      // 请求完成后将计数减少
      let count = requestCountMap.get(config.url);
      if (count > 0) {
        requestCountMap.set(config.url, --count);
      }
    }
    console.log("响应拦截器")
    return res
}, err => {
	// 响应拦失败拦截
	if (err.config.url.indexOf('B接口地址') > -1) {
      // 请求错误处理
      const config = err.config;
      // 请求完成后将计数减少
      let count = requestCountMap.get(config.url);
      if (count > 0) {
        requestCountMap.set(config.url, --count);
      }
    }
    return Promise.reject(err)
})  

这种方法虽然拦截了部分请求,有效改善出现接口请求超时的问题,但是此方法拦截的是最新的请求,不符合用户想要看的是最新数据的需求,故不采用。

方法四

利用Promise模拟任务队列,从而实现请求池效果。

import axios from 'axios'

// 定义请求池主函数函数
export const handQueue = (
  reqs // 请求总数
) => {
  reqs = reqs || []

  const requestQueue = (concurrency) => {
    concurrency = concurrency || 6 // 最大并发数
    const queue = [] // 请求池
    let current = 0

	// 定义dequeue函数
    const dequeue = () => {
      while (current < concurrency && queue.length) {
        current++;
        const requestPromiseFactory = queue.shift() // 出列
        requestPromiseFactory()
          .then(() => { // 成功的请求逻辑
          })
          .catch(error => { // 失败
            console.log(error)
          })
          .finally(() => {
            current--
            dequeue()
          });
      }

    }
	// 定义返回请求入队函数
    return (requestPromiseFactory) => {
      queue.push(requestPromiseFactory) // 入队
      dequeue()
    }

  }

 // 试验
  const enqueue = requestQueue(6)

  for (let i = 0; i < reqs.length; i++) {
    enqueue(() => axios.get('/api/test' + i))
  }
}

只有有请求响应成功的同时才会有新的请求进来,极大的降低里服务器的压力。
方法四参考文档地址

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值