能否在Redux thunk/reducer中调用router.push?是否为最佳实践?
问题解答
1. 偶现分享上报失败的原因和处理方案
你遇到的1%上报失败不是代码逻辑bug,是浏览器默认行为导致的:点击原生<a>标签触发跳转时,浏览器会直接销毁当前页面的执行上下文,未完成的异步请求、待执行的任务队列会被直接掐断,这时候你dispatch出去的createShare请求还没发出去,就被浏览器cancel了。
不需要等待接口请求完成再触发跳转——等接口返回再响应用户操作会造成明显的点击卡顿,体验很差。正确处理方式选一种就行:
- 优先用
navigator.sendBeacon()发这类上报/埋点请求,这个API是浏览器专门为页面卸载场景的上报设计的,请求会被浏览器托管,就算当前页关了也能保证发出去,完全不需要等回调,稳定性最高。 - 如果你一定要用原来的axios请求,就在onClick里先调用
e.preventDefault()拦住a标签的默认跳转,dispatch请求之后等100~200ms(足够浏览器把请求头发出去),再手动打开分享页即可,不需要等接口响应。
另外你原来的分享链接有个隐藏bug:quote参数没有做URL编码,caption里如果有空格、&、中文这类特殊字符,分享出去的文案会被截断,记得套一层encodeURIComponent()。
2. 路由跳转能不能写在Reducer/Thunk里
- 绝对不能在Reducer里调用
router.push():Reducer的核心要求是纯函数,相同输入必须返回相同输出,不能有任何副作用(接口请求、路由跳转、本地存储操作都算副作用),写了直接违反Redux的核心设计,会导致状态不可预测、调试完全没法做。 - Thunk里写
router.push()技术上能跑,但完全不符合前端最佳实践:Thunk的职责是封装和全局状态相关的异步逻辑,路由跳转属于视图交互层的逻辑,把路由逻辑硬塞到Thunk里会造成严重的耦合:后续你想在其他不需要跳转的场景复用这个分享逻辑、或者单独给分享加错误提示,都要改Thunk代码,单元测试的时候还要额外mock路由实例,纯给自己加维护成本。
正确的做法是Thunk只负责处理接口请求和状态更新,路由跳转、开窗这类交互逻辑放到组件层处理,参考写法:
// thunk只做请求,不碰路由 export const createShare = createAsyncThunk( `${namespace}/createShare`, async (props: any) => { const { data } = await axios({ method: 'post', url: 'share', data: props, headers: { crossDomain: true }, }) return data } ) // 组件里处理交互逻辑 const handleShare = async (e) => { e.preventDefault(); try { await dispatch(createShare(shareParams)).unwrap(); // 这里随便写跳转、开窗逻辑,好改好测 router.push('/share-success'); } catch (err) { // 接口报错单独处理,比如弹提示 console.error('分享上报失败', err); } }
给你改好的分享按钮参考:
<a className="btn btn-sm btn-circle" target="_blank" rel="noopener noreferrer" href={`https://www.facebook.com/sharer/sharer.php?u=${ process.env.NEXT_PUBLIC_ENVIRONMENT == "prod" ? "https://tikex.com" : "https://tikex-staging.com" }/share/${organizationSlug}/${postId}"e=${encodeURIComponent(postSurveySchemaDTO?.caption || '')}`} onClick={() => { // 用sendBeacon发上报,不怕页面跳转打断 const payload = new Blob( [JSON.stringify({ tempUserId: "e3445c3b-5513-4ede-8229-d258ed4418ae", postId, })], { type: 'application/json' } ); navigator.sendBeacon('/api/share', payload); }} > Megosztom </a>
内容的提问来源于stack exchange,提问作者János
相关产品推荐
相关产品推荐

