React Native:事件监听无法获取useState更新值,sound对象为空
问题根源
- 事件监听器拿不到最新状态:useState的更新是异步的,旧的回调函数会捕获组件渲染时的state快照,导致无法获取最新的state值。
- 停止播放时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
相关产品推荐
相关产品推荐

