【发布时间】:2020-09-19 02:36:12
【问题描述】:
由于我想使用 React Context 设置 Axios 拦截器,唯一可行的解决方案是创建一个 Interceptor 组件,以便使用 useContext 挂钩访问 Context 状态和调度。
问题是,这会创建一个闭包,并在拦截器被调用时将旧数据返回给它。
我正在使用 React/Node 进行 JWT 身份验证,并且我正在使用 Context API 存储访问令牌。
这就是我的拦截器组件现在的样子:
import React, { useEffect, useContext } from 'react';
import { Context } from '../../components/Store/Store';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
const ax = axios.create();
const Interceptor = ({ children }) => {
const [store, dispatch] = useContext(Context);
const history = useHistory();
const getRefreshToken = async () => {
try {
if (!store.user.token) {
dispatch({
type: 'setMain',
loading: false,
error: false,
auth: store.main.auth,
brand: store.main.brand,
theme: store.main.theme,
});
const { data } = await axios.post('/api/auth/refresh_token', {
headers: {
credentials: 'include',
},
});
if (data.user) {
dispatch({
type: 'setStore',
loading: false,
error: false,
auth: store.main.auth,
brand: store.main.brand,
theme: store.main.theme,
authenticated: true,
token: data.accessToken,
id: data.user.id,
name: data.user.name,
email: data.user.email,
photo: data.user.photo,
stripeId: data.user.stripeId,
country: data.user.country,
messages: {
items: [],
count: data.user.messages,
},
notifications:
store.user.notifications.items.length !== data.user.notifications
? {
...store.user.notifications,
items: [],
count: data.user.notifications,
hasMore: true,
cursor: 0,
ceiling: 10,
}
: {
...store.user.notifications,
count: data.user.notifications,
},
saved: data.user.saved.reduce(function (object, item) {
object[item] = true;
return object;
}, {}),
cart: {
items: data.user.cart.reduce(function (object, item) {
object[item.artwork] = true;
return object;
}, {}),
count: Object.keys(data.user.cart).length,
},
});
} else {
dispatch({
type: 'setMain',
loading: false,
error: false,
auth: store.main.auth,
brand: store.main.brand,
theme: store.main.theme,
});
}
}
} catch (err) {
dispatch({
type: 'setMain',
loading: false,
error: true,
auth: store.main.auth,
brand: store.main.brand,
theme: store.main.theme,
});
}
};
const interceptTraffic = () => {
ax.interceptors.request.use(
(request) => {
request.headers.Authorization = store.user.token
? `Bearer ${store.user.token}`
: '';
return request;
},
(error) => {
return Promise.reject(error);
}
);
ax.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
console.log(error);
if (error.response.status !== 401) {
return new Promise((resolve, reject) => {
reject(error);
});
}
if (
error.config.url === '/api/auth/refresh_token' ||
error.response.message === 'Forbidden'
) {
const { data } = await ax.post('/api/auth/logout', {
headers: {
credentials: 'include',
},
});
dispatch({
type: 'resetUser',
});
history.push('/login');
return new Promise((resolve, reject) => {
reject(error);
});
}
const { data } = await axios.post(`/api/auth/refresh_token`, {
headers: {
credentials: 'include',
},
});
dispatch({
type: 'updateUser',
token: data.accessToken,
email: data.user.email,
photo: data.user.photo,
stripeId: data.user.stripeId,
country: data.user.country,
messages: { items: [], count: data.user.messages },
notifications:
store.user.notifications.items.length !== data.user.notifications
? {
...store.user.notifications,
items: [],
count: data.user.notifications,
hasMore: true,
cursor: 0,
ceiling: 10,
}
: {
...store.user.notifications,
count: data.user.notifications,
},
saved: data.user.saved,
cart: { items: {}, count: data.user.cart },
});
const config = error.config;
config.headers['Authorization'] = `Bearer ${data.accessToken}`;
return new Promise((resolve, reject) => {
axios
.request(config)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
}
);
};
useEffect(() => {
getRefreshToken();
if (!store.main.loading) interceptTraffic();
}, []);
return store.main.loading ? 'Loading...' : children;
}
export { ax };
export default Interceptor;
如果 cookie 中有刷新令牌,则每次用户刷新网站以检索访问令牌时调用 getRefreshToken 函数。
interceptTraffic 函数是问题仍然存在的地方。
它由一个请求拦截器和一个响应拦截器组成,请求拦截器为每个请求附加一个带有访问令牌的标头,以及一个响应拦截器,用于处理访问令牌过期,以便使用刷新令牌获取新的。
您会注意到我正在导出 ax(我添加了拦截器的 Axios 的一个实例),但是当它在此组件之外被调用时,由于关闭,它引用了旧的存储数据。
这显然不是一个好的解决方案,但这就是为什么我需要帮助组织拦截器,同时仍然能够访问上下文数据。
请注意,我将此组件创建为包装器,因为它呈现提供给它的子组件,这是主 App 组件。
感谢任何帮助,谢谢。
【问题讨论】:
-
其实我觉得你用的方法还不错。如果你想让代码更简洁,你可以将一些逻辑(对象映射、Axios 请求)提取到不同的函数中。通常,您作为 Provider 的 Auth 中间件将完成这项工作!
-
您最终找到了一个好的解决方案吗?我有一个类似的问题......但由于某种原因,我从我的上下文中获取 accessToken,有时我做对了,有时我得到它 NULL,我完全不明白
标签: javascript reactjs axios jwt jwt-auth