ReactJs环境下token过期自动调用token API及按需重渲染实现问题
实现token自动过期刷新+自动重渲染方案
现有代码问题梳理
- 时间比较逻辑错误:用12小时制的时分秒字符串做比较,跨天场景会完全失效,应该用时间戳(毫秒级数值)做比较才准确
- 仅在组件挂载时检查一次过期:没有持续监听token过期状态,没法实现到期自动触发刷新
- setState异步坑:调用setState后直接取
this.state拿不到最新值,存sessionStorage应该直接用接口返回的响应数据 - 方法this指向丢失:类组件里自定义方法如果不用箭头函数或者手动bind,调用时this会指向undefined
完整实现代码
下面的实现会在token到期时自动触发刷新,state更新后会自动触发组件重渲染:
import React from 'react'; import axios from 'axios'; class ApiCall extends React.Component { constructor(props) { super(props); this.state = { token: '', expiry: 0 // 存13位毫秒级时间戳 }; this.refreshTimer = null; // 存定时器id,卸载时清除 } // 用箭头函数固定this指向 tokenApiCall = () => { axios .post( 'https://api/v1/Getauthtoken', { username: 'xyz', client: 'XYZ', } ) .then((res) => { // 接口返回的expires_in一般是有效秒数,计算出过期时间戳 const expiryTimestamp = Date.now() + res.expires_in * 1000; // 更新state,会自动触发重渲染 this.setState({ token: res.token, expiry: expiryTimestamp }); // 直接用接口返回的值存缓存,不要用this.state(setState是异步的) sessionStorage.setItem('tokenval', res.token); sessionStorage.setItem('tokenExp', expiryTimestamp); // 刷新后设置下一次到期自动刷新的定时器 this.setRefreshTimer(expiryTimestamp); }) .catch((error) => { console.log('Error message is ' + error); }); } // 设置到期自动刷新定时器 setRefreshTimer = (expiryTimestamp) => { // 先清除之前的定时器避免重复 if (this.refreshTimer) clearTimeout(this.refreshTimer); // 计算还有多久过期,提前100ms刷新避免刚好到期时请求失效 const delay = expiryTimestamp - Date.now() - 100; if (delay > 0) { this.refreshTimer = setTimeout(() => { this.tokenApiCall(); }, delay); } else { // 已经过期直接刷新 this.tokenApiCall(); } } componentDidMount() { const localToken = sessionStorage.getItem('tokenval'); const localExpiry = Number(sessionStorage.getItem('tokenExp')); if (!localToken || !localExpiry) { // 本地没有缓存直接请求新token this.tokenApiCall(); } else { if (Date.now() > localExpiry) { // 已经过期直接刷新 this.tokenApiCall(); } else { // 本地token有效,更新state触发重渲染,同时设置定时器 this.setState({ token: localToken, expiry: localExpiry }); this.setRefreshTimer(localExpiry); } } } // 组件卸载时清除定时器避免内存泄漏 componentWillUnmount() { if (this.refreshTimer) clearTimeout(this.refreshTimer); } render() { // state里的token更新会自动触发render重渲染,需要用token的逻辑直接取this.state.token即可 return <div>当前token:{this.state.token}</div> } } export default ApiCall;
额外优化建议
如果你的token是给全局接口请求用的,更推荐搭配axios请求拦截器实现:每次发送请求前先检查token是否过期,如果过期先暂停当前请求,刷新token后再继续发送请求,不需要轮询/定时器,性能更优。
内容的提问来源于stack exchange,提问作者Shubhangi Pant
相关产品推荐
相关产品推荐

