【MoodVine】react函数组件中使用useState改变值后立刻获取最新值

我们面临的问题是在React函数组件中,当我们通过useState更新了一个状态(这里是audioUrl)后,我们希望立刻获取到最新的值来播放语音。但useState的更新是异步的,在更新后立即访问状态变量并不能获取到最新值。本文将介绍三种尝试方式(其中两种失败,一种成功)以及最终的解决方案。

问题背景

在实现一个聊愈功能时,我们需要获取输出的语音链接并进行播放。我们使用audioUrl状态来存储语音链接,并通过handleAudioToggle方法进行播放。每次获取到新的语音链接后,我们会更新audioUrl状态,然后调用handleAudioToggle来播放。然而,我们发现更新状态后立即调用播放函数时,audioUrl的值尚未更新,导致播放失败(例如404错误,因为新链接可能还未准备好)。

因为在react合成事件中改变状态是异步的,出于减少render次数,react会收集所有状态变更,然后比对优化,最后做一次变更;

尝试一:直接赋值(将newUrl直接传递给播放组件)

我们尝试在设置状态的同时,直接将新的URL传递给播放函数,避免依赖状态的当前值。这样我们就能确保传递给播放函数的是最新的URL。

const [audioUrl, setAudioUrl] = useState(''); \\\\不使用useState
const fetchNewAudio = () => {
  const newUrl = replaceDomain(
        textChat.data.data.text,
        "pub-a3b************b2287.r2.dev"
  );
  handleAudioToggle(newUrl); //直接将url传递给播放函数
};

这个方法在传递URL时是即时的,所以播放函数使用的是最新的URL。但是,由于我们获取到新URL时,语音文件可能还未上传完成或生成,此时调用播放会导致404错误。所以,虽然状态更新和播放函数调用时URL是最新的,但资源未准备好,播放仍会失败。

尝试二:使用useEffect监听状态变化(无效)

我们首先想到使用useEffect来监听audioUrl的变化。当audioUrl变化时,在useEffect中调用播放函数。这样应该能够确保在状态更新后播放。

const [audioUrl, setAudioUrl] = useState('');

useEffect(() => {
  if (audioUrl) {
    handleAudioToggle(audioUrl); // 在audioUrl变化后调用播放
  }
}, [audioUrl]);

// 在获取到新语音链接的地方
const fetchNewAudio = () => {
  const newUrl = replaceDomain(
        textChat.data.data.text,
        "pub-a3b9222a444c40648c0a11b32ecb2287.r2.dev"
  );
  setAudioUrl(newUrl); // 设置新的语音链接
};

实际上,我们发现这个方法是无效的。因为当audioUrl被设置后,useEffect确实会在状态更新后触发,但是此时新的语音链接可能还未在服务器上生成(即链接设置好,但资源还没有准备好),此时立即播放会导致404。注意:这个尝试并不是为了解决异步更新值的问题(实际上它解决了状态异步更新的问题),而是因为资源未准备好而无法播放。

尝试三:使用Promise延迟播放(有效)

为了解决资源未准备好就播放的问题,我们尝试在设置新URL后延迟一段时间再播放。这样能够给服务器足够的时间生成语音文件并存储。

// 创建一个延迟函数
function delay(timeout) {
    return new Promise(resolve => {
        setTimeout(() => {
        resolve(); // 3秒后解析Promise
        }, timeout);
    });
    }
 const fetchNewAudio = async () => {
  const newUrl = getNewAudioUrl(); // 获取新的语音链接
  setAudioUrl(newUrl); // 更新状态

  // 延迟5秒播放
  delay(5000)
     .then(() => {
         handleAudioToggle(newUrl); 
         console.log("成功触发音频切换");
      })
      .catch(error => {
         console.error("延迟执行失败:", error);
      });
};

延迟播放给了服务器足够的时间去生成语音文件。在这段时间内,语音文件可能已经上传完成并可以通过URL访问,所以当之后调用播放时,资源已经准备就绪,播放成功。经过测试,3秒时间一半语音文件能准备就绪,5秒几乎所有文件均能准备就绪

总结

  1. 问题核心useState的更新是异步的,我们无法在设置状态后立即获取最新值。而且,即使我们能够获取到最新值,由于服务器生成语音文件需要时间,立即播放也会导致404。
  2. 解决方案:我们通过延迟播放的方式给服务器预留处理时间。同时,我们可以通过直接使用获取到的新URL(而不是依赖状态的最新值)来播放,避免状态异步更新的问题。
  3. 更优的实践:在需要播放新语音的地方,我们不应该依赖状态的最新值,而是直接使用我们刚刚获得的新URL。因为:
    • 状态更新是异步的,在同一个函数作用域内不会立即改变。
    • 我们恰好需要延迟播放,所以我们可以将新URL保存在一个变量中(或者使用ref保存),然后在延迟后使用它来播放,这样既避免了状态异步更新的问题,又确保了播放时使用正确的URL。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值