由于 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
并没有接收到新数据,因此列表也没有重新渲染,请问这种情况怎么处理,或者有什么别的解决方案吗
1
shakukansp 2023-07-12 11:57:40 +08:00
首先你这个 list 不是响应式数据, 要用 ref 包一下
看下 vueuse 里面的 useAxios 的思路,或者 useFetch ,或者看下 vue-request |
2
lisongeee 2023-07-12 12:00:31 +08:00
```ts
const list = ref([]) getDataFromApi().then(data=>{ list.value = data }) ``` |
3
rain0002009 2023-07-12 12:03:42 +08:00
axios 拦截器是可以返回 promise 的 所以用 async await 改造一下不就好了
|
4
bhbhxy OP @shakukansp 这是为了方便起见随手敲的代码,原始代码是 const list = reactive({list:[]})
|
5
shakukansp 2023-07-12 12:21:57 +08:00
@bhbhxy 你这自然不行
const list = 既然是 const 那么就该意识到这个变量的引用地址不能修改,改的话直接报错 那你改 reactive 里面的 list 为啥不报错,说明改了以后 reactive 里面的 list 和外面 const 声明的 list 不是指向同一个内存地址了 |
6
bhbhxy OP @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 还是原来的空值 |
7
yetrun 2023-07-12 14:34:01 +08:00
示例代码还是得认真写一下,不然看的人会产生歧义。我举个例子:
getNewToken() // 这个是异步吗? return axios.request(response.config); 如果是异步,整个代码结构就不对了,改成下面的格式: await getNewToken() return await axios.request(response.config) |
8
bhbhxy OP @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 |
9
yetrun 2023-07-12 15:04:57 +08:00
@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 写进去了吗? |
10
bhbhxy OP @yetrun 发送新的 token 后数据已经获取到了,但这个操作只是重发了请求,并没有给组件内的 list 赋值,因为这一步是在组件外发起的请求,所以应该怎么赋值给组件内的 list
|
11
yetrun 2023-07-12 15:20:54 +08:00
@bhbhxy 你的问题是因为 token 过期导致 list 赋值失败,还是单纯地想问如何给 list 赋值这个问题?如果 token 没过期,list 赋值正常吗?
|
12
bhbhxy OP @yetrun token 有效的情况下,list 赋值正常,token 过期后由于是在拦截器层面重发的请求,导致组件内部的 list 没有感知到数据的变化,怎么给重发请求后的 list 赋上值
|
13
yetrun 2023-07-12 15:30:28 +08:00
@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); } }); 要写在第二个参数。 |
15
bhbhxy OP @yetrun onMounted(() => fetchData())组件获取数据在页面初始化时只执行一次,在 token 失效的情况下,这个请求返回的是 401 未授权,所以由拦截器获取新 token 后重发一次请求获取正确数据,这时候 onMounted 里的方法不会再执行了,也就是没有机会给 list 赋值。重发请求是指再请求一遍接口的 url ,而不是再执行一次组件内的 fetchData
|
17
zzzzhan 2023-07-12 16:06:46 +08:00
@bhbhxy #8 return service.request(error.response.config) 重发的请求进入到 axios 的拦截器了吗
|
18
bhbhxy OP @zzzzhan 获取新 token 后就是正常接口数据了,是走的正常 response 拦截器,token 失效走的 error
|
19
shakukansp 2023-07-12 16:23:40 +08:00
@bhbhxy 一串楼看下来,你还是需要看我 1 楼说的那几个库
|
20
shakukansp 2023-07-12 16:28:25 +08:00
简单地说就是你需要把请求的结果转成响应式变量
const { data: tableData, loading, send: refetch } = useAxios(axios.get('xxxxx')) 类似这种东西 这里面的 data 本身就是个响应式变量,useAxios 内的 async 函数每次运行都会更新这个 data, 你把这个 data 直接当 list 用或者 computed 处理一下格式 send 是手动重新触发请求 |
21
summerLast 2023-07-12 16:36:09 +08:00
@bhbhxy 第一次的 list 是什么还是就是 401
|
22
bhbhxy OP @shakukansp 专门为这个引入一个库是否增加开发成本
|
23
bhbhxy OP @summerLast token 没失效是正常的,失效了才返回 401 ,这时候再去获取新 token ,然后重发之前的请求
|
24
shakukansp 2023-07-12 16:44:35 +08:00
@bhbhxy 自己写个简单的也行,但是你自己写的肯定没封装好的考虑全面
|
25
summerLast 2023-07-12 16:45:11 +08:00 1
@bhbhxy 建议直接 打印 ((await getDataFromApi()).data ), 看看哪块报错了,伪代码看着没有逻辑上的问题 建议 axios.request(reponse.config) 增加上 await
|
26
shakukansp 2023-07-12 16:48:13 +08:00
const useRequest = (fn) => {
const data = ref() const send = async () => { data.value = await fn } return { data, send } } |
27
shakukansp 2023-07-12 16:48:58 +08:00
里面的 fn 加个()
|
28
wali77 2023-07-12 16:55:34 +08:00
|
29
zcf0508 2023-07-12 16:55:56 +08:00 via Android 1
不是响应式的问题吧,你要处理的是看下拦截器为什么没有返回数据
|
30
bhbhxy OP @shakukansp 感谢感谢,我好像弄明白了
|
32
shakukansp 2023-07-12 17:04:56 +08:00
@bhbhxy 这个是大概思路,实际上还要处理错误啥的
|
33
Ng8023 2023-07-12 17:07:57 +08:00 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) => { // 处理错误 } ) |
34
liberty1900 2023-07-13 00:16:16 +08:00 1
|
35
mingl0280 2023-07-13 01:26:22 +08:00 via Android
@bhbhxy 外部代码需要引用特定 component 的实例才能修改实例内部数据。你自己没把实例保存下来吧?我就没看见你引用 childComponent 的实例的。
|
36
webszy 2023-08-19 08:05:04 +08:00 via Android
真的很简单,想要触发 v–for 的重绘,可以修改 key
|