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

如何将引用audio标签的useRef值存储到Redux Toolkit中?

解决方案:避免在Redux中存储不可序列化的DOM引用

错误原因

你遇到的A non-serializable value was detected in an action错误,是因为Redux要求action的payload和store中的状态必须是可序列化的值(比如字符串、数字、普通对象、数组这类能转成JSON的类型),而audioRef是React的Ref对象,其current指向的是DOM音频元素,属于浏览器原生对象,无法被序列化,所以不能直接存在Redux里。

可行方案

方案1:用Redux管理音频状态,而非DOM引用

核心思路是:Redux只存储音频的状态数据(比如播放状态、当前音频URL、进度、时长等可序列化的值),而音频DOM元素的操作统一放在它所在的组件(你的App.jsx)中,通过监听Redux状态变化来执行对应的DOM操作。

步骤示例:

  1. 定义Redux Slice,管理音频相关状态:
// features/audio/audioSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  isPlaying: false,
  currentAudioUrl: '',
  currentTime: 0,
  duration: 0
};

const audioSlice = createSlice({
  name: 'audio',
  initialState,
  reducers: {
    setIsPlaying: (state, action) => {
      state.isPlaying = action.payload;
    },
    setCurrentAudioUrl: (state, action) => {
      state.currentAudioUrl = action.payload;
    },
    setCurrentTime: (state, action) => {
      state.currentTime = action.payload;
    },
    setDuration: (state, action) => {
      state.duration = action.payload;
    }
  }
});

export const { setIsPlaying, setCurrentAudioUrl, setCurrentTime, setDuration } = audioSlice.actions;
export default audioSlice.reducer;
  1. 在App.jsx中监听Redux状态,控制音频元素:
// App.jsx
import { useRef, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setIsPlaying, setDuration, setCurrentTime } from './features/audio/audioSlice';

function App() {
  const audioRef = useRef(null);
  const dispatch = useDispatch();
  const { isPlaying, currentAudioUrl } = useSelector(state => state.audio);

  // 监听播放状态变化,控制音频播放/暂停
  useEffect(() => {
    if (!audioRef.current) return;
    isPlaying ? audioRef.current.play().catch(err => console.error('播放失败:', err)) : audioRef.current.pause();
  }, [isPlaying]);

  // 监听音频URL变化,加载新音频
  useEffect(() => {
    if (!audioRef.current || !currentAudioUrl) return;
    audioRef.current.src = currentAudioUrl;
    audioRef.current.load();
  }, [currentAudioUrl]);

  // 监听音频时长、进度变化,同步到Redux
  useEffect(() => {
    const handleLoadedMetadata = () => dispatch(setDuration(audioRef.current.duration));
    const handleTimeUpdate = () => dispatch(setCurrentTime(audioRef.current.currentTime));
    
    if (audioRef.current) {
      audioRef.current.addEventListener('loadedmetadata', handleLoadedMetadata);
      audioRef.current.addEventListener('timeupdate', handleTimeUpdate);
    }

    return () => {
      if (audioRef.current) {
        audioRef.current.removeEventListener('loadedmetadata', handleLoadedMetadata);
        audioRef.current.removeEventListener('timeupdate', handleTimeUpdate);
      }
    };
  }, [dispatch]);

  return (
    <div>
      <audio ref={audioRef} />
      {/* 其他组件 */}
    </div>
  );
}

export default App;
  1. 其他组件通过dispatch控制音频:
    比如在播放按钮组件中:
// components/PlayButton.jsx
import { useDispatch, useSelector } from 'react-redux';
import { setIsPlaying } from '../features/audio/audioSlice';

function PlayButton() {
  const dispatch = useDispatch();
  const isPlaying = useSelector(state => state.audio.isPlaying);

  return (
    <button onClick={() => dispatch(setIsPlaying(!isPlaying))}>
      {isPlaying ? '暂停' : '播放'}
    </button>
  );
}

export default PlayButton;

方案2:用独立模块存储音频引用(适合简单场景)

如果你的应用不需要复杂的状态管理,只是想让多个组件直接访问音频实例,可以创建一个单独的模块来导出这个ref,不需要经过Redux:

  1. 创建audioInstance.js:
// utils/audioInstance.js
import { createRef } from 'react';

export const audioRef = createRef(null);
  1. 在App.jsx中使用这个ref:
// App.jsx
import { audioRef } from './utils/audioInstance';

function App() {
  return (
    <div>
      <audio ref={audioRef} />
      {/* 其他组件 */}
    </div>
  );
}

export default App;
  1. 在其他组件中直接导入使用:
// components/VolumeControl.jsx
import { audioRef } from '../utils/audioInstance';

function VolumeControl() {
  const handleVolumeChange = (e) => {
    audioRef.current && (audioRef.current.volume = e.target.value);
  };

  return (
    <input
      type="range"
      min="0"
      max="1"
      step="0.1"
      onChange={handleVolumeChange}
    />
  );
}

export default VolumeControl;

总结

  • Redux的设计初衷是存储可序列化的应用状态数据,不是用来存储DOM引用这类不可序列化的值。
  • 优先选择方案1,它更符合Redux的设计理念,也能更好地管理音频相关的状态逻辑;方案2适合小型应用或简单场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 08:56:06