React 18严格模式开发环境下重复渲染导致一次性授权码失效的问题及最优解决方案咨询
我最近深入研究了React 18严格模式下的组件重复挂载(mount -> unmount -> re-mount)行为,对网上找到的各种“hack式”解决方案都不满意,自己也没找到理想的处理方式,想和大家探讨下这个被讨论得不多的场景。
背景
React 18的严格模式新增了组件挂载-卸载-重新挂载的行为,目的是检测组件代码中的不纯操作,但这也给不少开发者带来了困惑,尤其是某些天生就无法纯函数化的业务场景。
问题
大部分场景下我们可以让组件逻辑保持纯函数化,所以这种重复挂载行为不会有问题,但有些任务天生就是不纯的,目前行业里的解决方案要么增加复杂度,要么需要引入额外的npm包,总感觉是治标不治本。
实际场景示例
我遇到的是一个非常常见的授权流程:需要把一次性的授权码发送到私有API换取访问令牌。这个授权码只能使用一次,服务器会直接拒绝重复使用同一授权码的请求,所以这个操作天生就是不纯的,根本没法改成纯函数逻辑。
具体的流程是这样的:
- 应用加载时,根据初始Redux状态,发起GET请求到私有代理API,尝试通过httpOnly的刷新令牌Cookie获取访问令牌
- 如果请求失败,用户会被导向登录页;成功的话就留在当前私有页面
- 登录页会检查URL中的
code查询参数:- 如果存在,就发起POST请求用这个code换取访问令牌和新的刷新令牌
- 如果不存在,就重定向到认证服务商的登录UI,用户输入凭证后,服务商又会把用户重定向回我的登录页并带上
code参数
网上找到的现有解决方案及我的看法
我搜了不少方案,但都觉得不满意,下面简单分析下,希望能找到真正解决问题的办法,而不是这些“临时补丁”:
方案1:使用第三方请求库
比如React Query、useSWR这类库,靠它们的缓存和请求去重机制来避免重复调用。
这算是官方“推荐”的方案,但我完全不认同——本质上只是用额外的依赖掩盖问题,还会增加包体积。而且这并没有让操作变得纯函数化,反而违背了React官方说的“不要试图阻止重复调用,而是让重复调用不产生影响”的建议。但在这个场景下根本做不到“不产生影响”,毕竟授权码用一次就失效了。
方案2:使用useRef
用ref维护一个本地状态,通过检查ref的值来决定是否执行令牌交换请求。
这是目前我觉得相对最干净的方案,复杂度低,也不会增加包体积,但缺点是不够直观,需要开发者对React的底层机制有较深理解,后续维护起来可能有门槛。
方案3:缓存请求响应
这明显有安全问题——如果缓存了响应,攻击者可以通过复制请求来窃取访问令牌,风险太高。
方案4:关闭严格模式
虽然听起来离谱,但如果团队成员对React的理解足够到位,这反而可能是最直接的方案。毕竟严格模式的重复渲染是React团队的设计选择,但它带来的最大问题是开发环境和生产环境的行为不一致,而且关闭后就失去了严格模式这个有用的安全检测工具。
我的思考与求助
目前我还没找到完全满意的解决方案,但我想或许可以开发一个标准的Hook来处理这类天生不纯的操作——比如把方案2封装成通用Hook?不过可能我没想到更好的思路,比如有没有什么地方可以放置令牌交换逻辑,让它不会被触发两次?
非常欢迎大家给出建议,目前我暂时打算先用方案2,因为它是最不“激进”的临时方案。
备注:内容来源于stack exchange,提问作者William Iehl

