You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native页面离开时如何终止声音生成、下载与播放函数?

解决React Native中页面卸载时终止声音生成/下载的问题

你的核心问题出在三个地方:AbortController的闭包引用错误、异步操作未绑定中断信号、状态检查时机不对,导致页面卸载后生成/下载流程仍继续执行。

关键问题分析

  1. AbortController的闭包陷阱:你把AbortController存在useState中,但setState是异步的,组件卸载时清理函数里的abortController可能还是初始的undefined,调用abort()根本没生效。
  2. 异步操作未关联中断信号:生成、下载是耗时异步操作,你只在同步步骤检查aborted状态,异步操作启动后这些检查不会触发,也没把AbortSignal传递给请求(比如fetch),无法真正中断异步任务。
  3. 未处理卸载后的状态更新:组件卸载后仍调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 23:10:55