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

React Native:事件监听无法获取useState更新值,sound对象为空

React Native PlayAudioButton组件问题修复方案

问题根源

  1. 事件监听器拿不到最新状态:useState的更新是异步的,旧的回调函数会捕获组件渲染时的state快照,导致无法获取最新的state值。
  2. 停止播放时sound为null:setSound是异步更新操作,点击停止按钮时组件还未完成重渲染,state里的sound仍保持初始的null值。

修复代码

用useRef替代useState存储sound对象,useRef.current是可变引用,能同步拿到最新的实例,不受闭包和异步更新的限制:

import React, { useRef } from "react";
import { View, TouchableOpacity } from "react-native";
import Sound from "react-native-sound";
import PlayIcon from "../svg/PlayIcon";
import StopIcon from "../svg/StopIcon";
import { scale } from "react-native-utils-scale";

const PlayAudioButton = (props) => {
  const { url, action, playingId, setPlayingId } = props;
  // 用useRef存储sound实例,替代useState
  const soundRef = useRef(null);

  const onPlayButtonPress = () => {
    // 先清理之前的音频实例,避免多音频同时播放或内存泄漏
    if (soundRef.current) {
      soundRef.current.stop();
      soundRef.current.release();
      soundRef.current = null;
    }

    setPlayingId(url);
    const fullUrl = `${url?.startsWith("/") ? url?.slice(1) : url}`;
    const newSound = new Sound(fullUrl, "", (error) => {
      if (error) {
        console.log("Error loading sound", error);
        setPlayingId(null);
        soundRef.current = null;
      } else {
        console.log("Audio Started");
        newSound.play(() => {
          newSound.release();
          soundRef.current = null;
          setPlayingId(null);
          console.log("Audio stopped");
        });
      }
    });
    // 直接更新ref的current,同步获取最新实例
    soundRef.current = newSound;
  };

  const onStopButtonPress = () => {
    setPlayingId(null);
    // 直接访问ref.current拿到最新的sound实例
    if (soundRef.current) {
      soundRef.current.stop();
      soundRef.current.release();
      soundRef.current = null;
    }
  };

  return (
    <View
      style={{
        justifyContent: "center",
        paddingHorizontal: scale(4),
      }}
    >
      {(url !== playingId || playingId === null) ? (
        <TouchableOpacity
          style={{
            flex: 1,
            justifyContent: "center",
            alignItems: "center",
            alignSelf: "stretch",
            zIndex: 3,
          }}
          onPress={() => {
            onPlayButtonPress();
            action?.();
          }}
        >
          <PlayIcon color="#011132" />
        </TouchableOpacity>
      ) : (
        <TouchableOpacity
          style={{
            flex: 1,
            justifyContent: "center",
            alignItems: "center",
            alignSelf: "stretch",
            zIndex: 3,
          }}
          onPress={() => {
            onStopButtonPress();
            action?.();
          }}
        >
          <StopIcon color="#c79546" />
        </TouchableOpacity>
      )}
    </View>
  );
};

export default PlayAudioButton;

关键修改说明

  • useRef替代useState:useRef的current属性不会触发组件重渲染,也不会被闭包捕获旧值,能在任何回调中获取最新的sound实例。
  • 播放前清理旧实例:避免同一时间播放多个音频,同时防止内存泄漏。
  • 完善错误处理:音频加载失败时重置界面状态和sound引用,避免界面异常。
  • 简化action调用:用可选链action?.()替代冗余的三元表达式,代码更简洁。

内容的提问来源于stack exchange,提问作者Khaled Ahmed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 00:22:14