V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
bhbhxy
V2EX  ›  Vue.js

axios 重发请求后数据如何重新渲染

  •  
  •   bhbhxy · 290 天前 · 2699 次点击
    这是一个创建于 290 天前的主题,其中的信息可能已经有所发展或是发生改变。

    由于 token 过期,在获取新 token 后使用 axios 重新发送之前的请求,数据也传回来了,但是现在有一个问题,列表数据无法显示了,页面的结构是这样的 index.vue

    <template>
    	<ChildComponent />
    </template>
    

    我是在<ChildComponent />中请求的数据:

    	<template>
        	<ul>
            	<li v-for="item in list">{{item}}</li>
            </ul>
        </template>
        <script>
    		const list = getDataFromApi().data
        </script>
    

    重发请求写在拦截器里:

    axios.interceptors.response.use(
    	response => {
    		//请求重发
            if(statusCode === 401) {
            	getNewToken()
    			return axios.request(response.config);
            }
    	}	
    })
    

    请求是重发了,但是组件里的list并没有接收到新数据,因此列表也没有重新渲染,请问这种情况怎么处理,或者有什么别的解决方案吗

    36 条回复    2023-08-19 08:05:04 +08:00
    shakukansp
        1
    shakukansp  
       290 天前
    首先你这个 list 不是响应式数据, 要用 ref 包一下
    看下 vueuse 里面的 useAxios 的思路,或者 useFetch ,或者看下 vue-request
    lisongeee
        2
    lisongeee  
       290 天前
    ```ts
    const list = ref([])
    getDataFromApi().then(data=>{
    list.value = data
    })
    ```
    rain0002009
        3
    rain0002009  
       290 天前
    axios 拦截器是可以返回 promise 的 所以用 async await 改造一下不就好了
    bhbhxy
        4
    bhbhxy  
    OP
       290 天前
    @shakukansp 这是为了方便起见随手敲的代码,原始代码是 const list = reactive({list:[]})
    shakukansp
        5
    shakukansp  
       290 天前
    @bhbhxy 你这自然不行

    const list =
    既然是 const 那么就该意识到这个变量的引用地址不能修改,改的话直接报错

    那你改 reactive 里面的 list 为啥不报错,说明改了以后 reactive 里面的 list 和外面 const 声明的 list 不是指向同一个内存地址了
    bhbhxy
        6
    bhbhxy  
    OP
       289 天前
    @shakukansp 不关注语法层面,我只是随手写了一下,ChildComponent 在页面初始化时如果 token 有效,会返回{code:0, data: [......], msg:""},此时是展示数据的,如果 token 失效,会返回{code:-1, data:null, msg:'token invalid'},此时不展示数据,现在使用 axios 重发请求,只是重发,重发获取的数据怎么塞到这个组件的 list 里,用 prop 传值 然后 watch 监听?还是有更专业的做法,这样是不是意味着我要把组件自身维护的数据都提取到外部窗器中来
    return axios.request(response.config) 重发了请求,这个代码写在全局拦截器里,组件内是不知道重新发起了一次请求的,所以 list 还是原来的空值
    yetrun
        7
    yetrun  
       289 天前
    示例代码还是得认真写一下,不然看的人会产生歧义。我举个例子:

    getNewToken() // 这个是异步吗?
    return axios.request(response.config);

    如果是异步,整个代码结构就不对了,改成下面的格式:

    await getNewToken()
    return await axios.request(response.config)
    bhbhxy
        8
    bhbhxy  
    OP
       289 天前
    @yetrun 不关注语法层面,伪代码只是为了方便看出代码逻辑。
    我在封装好的 request.js 写了 axios 的拦截器:
    service.interceptors.response.use(
    async error => {
    if(statusCode === 401) {
    await getNewToken();
    return service.request(error.response.config) //重发请求
    }

    return Promise.reject(error);
    }
    )

    而我在组件内获取数据是这样写的:
    fetchData() {
    //调用接口获取数据
    //给响应式变量 list 赋值
    }
    onMounted(() => fetchData())

    在 token 有效的情况下,数据正确展示,如果 token 无效,就先到代码片段一中获取新 token 再发一次请求,这个请求获取到新数据后,怎么赋值给组件内的 list
    yetrun
        9
    yetrun  
       289 天前
    @bhbhxy 你的逻辑没问题,只能关注代码层面了。细节导致 Bug. 我只是举个例子,其他的地方你要注意。这个是 Debug ,不是帮你判断逻辑有没有问题。这个也不是伪代码,而是省略的代码。省略的代码可以,但突出的地方一定要突出,就像我上面提到的,该 await 的地方一定要 await ,否则整个结构就说不通了会导致错误。

    按照你现在的代码格式,大体上是没问题了。不过既然你用到了异步 async 了,那么不要再返回 Promise 了,使用 await 保持一致性:

    return await service.request(error.response.config)
    return await Promise.reject(error)

    如果还有问题,可能在这:

    return await service.request(error.response.config) // 新获取的 Token 写进去了吗?
    bhbhxy
        10
    bhbhxy  
    OP
       289 天前
    @yetrun 发送新的 token 后数据已经获取到了,但这个操作只是重发了请求,并没有给组件内的 list 赋值,因为这一步是在组件外发起的请求,所以应该怎么赋值给组件内的 list
    yetrun
        11
    yetrun  
       289 天前
    @bhbhxy 你的问题是因为 token 过期导致 list 赋值失败,还是单纯地想问如何给 list 赋值这个问题?如果 token 没过期,list 赋值正常吗?
    bhbhxy
        12
    bhbhxy  
    OP
       289 天前
    @yetrun token 有效的情况下,list 赋值正常,token 过期后由于是在拦截器层面重发的请求,导致组件内部的 list 没有感知到数据的变化,怎么给重发请求后的 list 赋上值
    yetrun
        13
    yetrun  
       289 天前
    @bhbhxy 我检查了一下 axios 拦截器的文档,发现确实和你的写法有点出入:

    axios.interceptors.response.use(function (response) {
    // 正常请求
    return response;
    }, async function (error) {
    // 异常请求
    if(statusCode === 401) {
    await getNewToken()
    return await axios.request(response.config);
    } else {
    return await Promise.reject(error);
    }
    });

    要写在第二个参数。
    bhbhxy
        14
    bhbhxy  
    OP
       289 天前
    @yetrun 不是语法问题,不然编译阶段都得报错,只是为了节省篇幅省略了这一部分
    bhbhxy
        15
    bhbhxy  
    OP
       289 天前
    @yetrun onMounted(() => fetchData())组件获取数据在页面初始化时只执行一次,在 token 失效的情况下,这个请求返回的是 401 未授权,所以由拦截器获取新 token 后重发一次请求获取正确数据,这时候 onMounted 里的方法不会再执行了,也就是没有机会给 list 赋值。重发请求是指再请求一遍接口的 url ,而不是再执行一次组件内的 fetchData
    yetrun
        16
    yetrun  
       289 天前
    @bhbhxy 我没说语法问题,说的是用法问题。你仔细看下我的回答啊。
    zzzzhan
        17
    zzzzhan  
       289 天前
    @bhbhxy #8 return service.request(error.response.config) 重发的请求进入到 axios 的拦截器了吗
    bhbhxy
        18
    bhbhxy  
    OP
       289 天前
    @zzzzhan 获取新 token 后就是正常接口数据了,是走的正常 response 拦截器,token 失效走的 error
    shakukansp
        19
    shakukansp  
       289 天前
    @bhbhxy 一串楼看下来,你还是需要看我 1 楼说的那几个库
    shakukansp
        20
    shakukansp  
       289 天前
    简单地说就是你需要把请求的结果转成响应式变量

    const { data: tableData, loading, send: refetch } = useAxios(axios.get('xxxxx')) 类似这种东西

    这里面的 data 本身就是个响应式变量,useAxios 内的 async 函数每次运行都会更新这个 data, 你把这个 data 直接当 list 用或者 computed 处理一下格式

    send 是手动重新触发请求
    summerLast
        21
    summerLast  
       289 天前
    @bhbhxy 第一次的 list 是什么还是就是 401
    bhbhxy
        22
    bhbhxy  
    OP
       289 天前
    @shakukansp 专门为这个引入一个库是否增加开发成本
    bhbhxy
        23
    bhbhxy  
    OP
       289 天前
    @summerLast token 没失效是正常的,失效了才返回 401 ,这时候再去获取新 token ,然后重发之前的请求
    shakukansp
        24
    shakukansp  
       289 天前
    @bhbhxy 自己写个简单的也行,但是你自己写的肯定没封装好的考虑全面
    summerLast
        25
    summerLast  
       289 天前   ❤️ 1
    @bhbhxy 建议直接 打印 ((await getDataFromApi()).data ), 看看哪块报错了,伪代码看着没有逻辑上的问题 建议 axios.request(reponse.config) 增加上 await
    shakukansp
        26
    shakukansp  
       289 天前
    const useRequest = (fn) => {
    const data = ref()
    const send = async () => {
    data.value = await fn
    }

    return {
    data,
    send
    }
    }
    shakukansp
        27
    shakukansp  
       289 天前
    里面的 fn 加个()
    wali77
        28
    wali77  
       289 天前
    zcf0508
        29
    zcf0508  
       289 天前 via Android   ❤️ 1
    不是响应式的问题吧,你要处理的是看下拦截器为什么没有返回数据
    bhbhxy
        30
    bhbhxy  
    OP
       289 天前
    @shakukansp 感谢感谢,我好像弄明白了
    bhbhxy
        31
    bhbhxy  
    OP
       289 天前
    @wali77 最近好用的不用梯子的 gpt 都不能访问了,感谢提供新资源🌹
    shakukansp
        32
    shakukansp  
       289 天前
    @bhbhxy 这个是大概思路,实际上还要处理错误啥的
    Ng8023
        33
    Ng8023  
       289 天前   ❤️ 1
    个人经验给出以下方案:
    ----
    1. 401 可以让后端包装在 200 状态内部(后端无论如何总是返回类似这样的结构), 这样就不会走到 onRejected 里面
    {code:number, message?:string, data?:any}
    ^^--- 401 可以在这里返回
    2. 拦截到 code===401 不着急返回, 根据业务处理进行处理:
    a. 发广播到登录(授权)组件重新授权(假定为异步)
    b. 拿到新的授权码(token), 重新发起本次请求(AxiosResponse<T>里面能获取到请求参数)
    c. 注意: 要处理无限递归授权问题, 可以给定重试次数限制

    ----
    伪代码:
    service.interceptors.response.use(
    (resp: AxiosResponse<vo.R>) => {
    const r = resp.data;
    if (r.code === 401) {
    // 检测重试上限
    if (++retryTimes >= MAX_RETRY_TIMES) {
    return Promise.reject("授权失败");
    }

    // 重新获取授权码
    // 拿到授权码并保存到状态管理
    await reqAuth();

    // 用新授权重试
    return axios(resp.config);
    }

    // 其他逻辑
    // 业务层只处理: {code:number, data?:any, message?:string} 结构
    return r;
    },
    (err: AxiosError) => {
    // 处理错误
    }
    )
    mingl0280
        35
    mingl0280  
       289 天前 via Android
    @bhbhxy 外部代码需要引用特定 component 的实例才能修改实例内部数据。你自己没把实例保存下来吧?我就没看见你引用 childComponent 的实例的。
    webszy
        36
    webszy  
       252 天前 via Android
    真的很简单,想要触发 v–for 的重绘,可以修改 key
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2777 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 457ms · UTC 05:44 · PVG 13:44 · LAX 22:44 · JFK 01:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.