从next/router与localStorage获取用户ID的异步加载问题求助
问题描述
我正在开发一个社交媒体项目,包含posts.js和[id].js两个页面:
- 在
posts.js中,点击指定div会跳转到用户个人页面,代码如下:
<div className={styles.userDetails} onClick={() => { router.push({ pathname : `/user/${props.user._id}`}) }} > </div>
- 在
[id].js页面中,userId需要通过两种方式获取:- 从其他用户页面跳转过来时,从
router.query.id中获取 - 用户查看自身个人资料时,从
localStorage中获取(用户登录时ID已存入localStorage)
- 从其他用户页面跳转过来时,从
我编写了以下代码,但页面加载时router.query.id会短暂处于undefined状态,导致userId为undefined,进而引发接口调用失败:
const [userId, setUserId] = useState() const getUserData = async () => { try { if (router.query.id != undefined ) { setUserId(router.query.id) } else { setUserId(getUser()) } const res = await instance.get(`/user/${userId}`) setUser(res.data) } catch(e) { console.log(e) } useEffect(() => { getUserData() getPosts() }, [router.query])
解决方案
方案1:同步获取userId后再发起请求
不要依赖useState存储的userId,直接在函数内先计算出当前有效的userId,再调用接口,避免异步更新导致的延迟问题:
const getUserData = async () => { try { // 优先取路由参数,没有则从localStorage获取 const currentUserId = router.query.id ?? getUser(); // 处理userId为空的极端情况(比如未登录访问自身资料) if (!currentUserId) { router.push('/login'); return; } // 用同步获取到的userId调用接口 const res = await instance.get(`/user/${currentUserId}`); setUser(res.data); // 可选:如果需要在state中保存userId,再更新状态 setUserId(currentUserId); } catch(e) { console.log(e); } }; useEffect(() => { getUserData(); getPosts(); }, [router.query]);
方案2:拆分逻辑,确保参数存在再执行
将getUserData改为接收targetUserId参数,在useEffect中先判断路由状态,再决定调用方式:
const [userId, setUserId] = useState(); const getUserData = async (targetUserId) => { try { const res = await instance.get(`/user/${targetUserId}`); setUser(res.data); } catch(e) { console.log(e); } }; useEffect(() => { let targetUserId; // 查看他人资料:路由参数存在时直接使用 if (router.query.id) { targetUserId = router.query.id; setUserId(targetUserId); getUserData(targetUserId); } else { // 查看自身资料:从localStorage获取 targetUserId = getUser(); if (targetUserId) { setUserId(targetUserId); getUserData(targetUserId); } else { // 未登录状态跳转登录页 router.push('/login'); } } getPosts(); }, [router.query]);
方案3:等待路由完全就绪后执行(Next.js专属)
如果使用Next.js,可以利用router.isReady或路由事件,确保路由参数完全加载后再执行逻辑:
import { useRouter } from 'next/router'; const router = useRouter(); const getUserData = async () => { try { const currentUserId = router.query.id ?? getUser(); if (!currentUserId) { router.push('/login'); return; } const res = await instance.get(`/user/${currentUserId}`); setUser(res.data); setUserId(currentUserId); } catch(e) { console.log(e); } }; useEffect(() => { // 路由已就绪直接执行 if (router.isReady) { getUserData(); getPosts(); } else { // 监听路由加载完成事件 const handleRouteComplete = () => { getUserData(); getPosts(); router.events.off('routeChangeComplete', handleRouteComplete); }; router.events.on('routeChangeComplete', handleRouteComplete); // 组件卸载时移除监听 return () => router.events.off('routeChangeComplete', handleRouteComplete); } }, [router.isReady, router.query]);
内容的提问来源于stack exchange,提问作者Unnikrishnan KJ
相关产品推荐
相关产品推荐

