React Native页面离开时如何终止声音生成、下载与播放函数?
解决React Native中页面卸载时终止声音生成/下载的问题
你的核心问题出在三个地方:AbortController的闭包引用错误、异步操作未绑定中断信号、状态检查时机不对,导致页面卸载后生成/下载流程仍继续执行。
关键问题分析
- AbortController的闭包陷阱:你把AbortController存在useState中,但setState是异步的,组件卸载时清理函数里的
abortController可能还是初始的undefined,调用abort()根本没生效。 - 异步操作未关联中断信号:生成、下载是耗时异步操作,你只在同步步骤检查
aborted状态,异步操作启动后这些检查不会触发,也没把AbortSignal传递给请求(比如fetch),无法真正中断异步任务。 - 未处理卸载后的状态更新:组件卸载后仍调用
setCurrentState,不仅会触发React警告,还可能让后续流程继续执行。
修正后的代码实现
import { Sound } from 'expo-av/build/Audio'; import { useEffect, useRef, useState } from 'react'; import { Button, Text, View } from 'react-native'; function MyComponent() { const [currentState, setCurrentState] = useState(''); const [sound, setSound] = useState<Sound>(); // 用ref存储AbortController,避免闭包问题 const abortControllerRef = useRef<AbortController | null>(null); // 跟踪组件是否挂载,防止卸载后更新状态 const isMountedRef = useRef(true); useEffect(() => { const controller = new AbortController(); abortControllerRef.current = controller; return () => { isMountedRef.current = false; // 直接用创建的controller执行中断,确保生效 controller.abort(); // 同时卸载当前播放的声音 sound?.unloadAsync(); }; }, [sound]); const doSomething = async () => { const controller = abortControllerRef.current; if (!controller || controller.signal.aborted) return; if (!isMountedRef.current) return; setCurrentState('Generating a sound...'); try { // 将中断信号传递给生成声音的请求 const generateRes = await fetch('你的声音生成接口地址', { signal: controller.signal, // 其他请求配置(如method、body等) }); if (!generateRes.ok) throw new Error('声音生成失败'); const generateData = await generateRes.json(); if (controller.signal.aborted || !isMountedRef.current) return; setCurrentState('Downloading the sound...'); // 将中断信号传递给下载请求 const downloadRes = await fetch(generateData.soundUrl, { signal: controller.signal, }); if (!downloadRes.ok) throw new Error('声音下载失败'); // 根据expo-av要求处理下载的资源,比如转成本地URI const soundUri = '处理后的本地文件路径'; if (controller.signal.aborted || !isMountedRef.current) return; setCurrentState('Playing the sound...'); // 加载并播放声音 const { sound: newSound } = await Sound.createAsync( { uri: soundUri }, { shouldPlay: true } ); setSound(newSound); } catch (err) { // 忽略中断操作导致的AbortError if ((err as Error).name !== 'AbortError' && isMountedRef.current) { setCurrentState('操作失败'); } } }; return ( <View> <Text>{currentState}</Text> <Button onPress={doSomething}>Do something</Button> </View> ); }
核心修改说明
- 用useRef存储AbortController:避免setState异步更新带来的闭包问题,组件卸载时清理函数能直接访问到创建的控制器,确保
abort()有效执行。 - 绑定中断信号到异步请求:将
controller.signal传递给fetch,调用abort()时会自动中断请求并抛出AbortError,真正终止异步任务。 - 跟踪组件挂载状态:用
isMountedRef避免在组件卸载后调用setCurrentState,防止React警告和无效的状态更新。 - 多阶段状态检查:每个异步操作完成后,都检查信号是否已中断或组件是否已卸载,及时终止后续流程。
内容的提问来源于stack exchange,提问作者Jeon
相关产品推荐
相关产品推荐

