React18 Strict Mode下如何仅调用一次清理函数?
React18 Strict Mode下避免清理函数重复执行的兼容方案
React18的Strict Mode在开发环境会触发组件mount->unmount->mount的流程,目的是提前暴露组件在重复挂载/卸载时可能出现的问题。但这种行为会导致某些依赖清理时机的代码(比如API请求的中止逻辑)意外触发,像你给出的代码中,全局的AbortController会在第一次模拟卸载时被中止,导致第二次挂载后的API请求刚发起就失败。
下面是两种兼容开发与生产环境的解决方法:
方案一:将资源实例放到useEffect内部(推荐)
把AbortController的创建移到useEffect回调内部,这样每次组件挂载都会生成新的控制器实例,模拟卸载时只会中止当前实例对应的请求,第二次挂载会使用全新的控制器,不会影响新的API调用:
import { useEffect } from 'react'; function MyComponent() { useEffect(() => { // 每次挂载都创建新的AbortController const abortController = new AbortController(); // 发起API请求,传入signal fetch('/api/target-data', { signal: abortController.signal }) .then(res => res.json()) .then(data => { // 处理请求结果 console.log('请求成功:', data); }) .catch(err => { // 忽略中止错误,只处理其他异常 if (err.name !== 'AbortError') { console.error('请求失败:', err); } }); // 清理函数只针对当前挂载周期的控制器 return () => { abortController.abort(); }; }, []); return <div>组件内容</div>; }
这种方式完全符合React Strict Mode的设计预期,让组件具备可重入性,无论是开发还是生产环境都能正常工作。
方案二:用Ref标记真实卸载场景
如果你必须将资源实例放在useEffect外部,可以通过Ref来区分Strict Mode的模拟卸载和真实组件卸载:
import { useEffect, useRef } from 'react'; function MyComponent() { const abortController = new AbortController(); // 标记是否为真实卸载 const isPermanentUnmount = useRef(false); useEffect(() => { // 发起API请求 fetch('/api/target-data', { signal: abortController.signal }) .then(res => res.json()) .then(data => console.log('请求成功:', data)) .catch(err => { if (err.name !== 'AbortError') { console.error('请求失败:', err); } }); return () => { // 仅在真实卸载时中止请求 if (isPermanentUnmount.current) { abortController.abort(); } }; }, []); // 监听组件是否真的要卸载(Strict Mode模拟卸载不会触发这个useEffect的清理) useEffect(() => { return () => { isPermanentUnmount.current = true; }; }, []); return <div>组件内容</div>; }
这种方式通过额外的Ref标记真实卸载状态,避免模拟卸载时误中止请求,但相比方案一,代码复杂度更高,仅在无法重构资源实例位置时使用。
内容的提问来源于stack exchange,提问作者Pytan
相关产品推荐
相关产品推荐

