
在 JSX 中用 map 渲染多个 组件时,若通过自定义对象传递 ref,会因 props 解构错误导致 undefined;根本解法是使用 React.forwardRef 将 ref 正确透传至底层 DOM 元素。
在 jsx 中用 `map` 渲染多个 `
在 React 函数组件中,普通函数组件默认无法接收 ref 属性——它不会自动出现在 props 中,而是作为第二个参数被单独传入。你当前代码中将 ref 作为普通字段(如 { src: '/fire.mp3', ref: createRef() })混入 audioRef 对象,并在 <audio audio="{audioRef}"></audio> 中传递,但 <audio></audio> 组件并未声明接收 ref,更未将其绑定到 <audio></audio> 元素上,因此 const { src, ref } = audioRef 中的 ref 实际是 undefined(因为 audioRef 是 audio prop 的值,而你传的是 audio={audioRef},但组件内部并未解构 props.audio,更未处理 ref 参数)。
✅ 正确做法是:
-
使用
React.forwardRef包装Audio组件,使其能显式接收并转发 ref; -
将音频数据(如
src)作为普通 props 传入,而非嵌套在audio字段中; -
在
forwardRef的回调中,将ref直接赋给<audio></audio>元素,确保 DOM 引用可达。
以下是修正后的完整实现:
import React, { useState, useRef, createRef, forwardRef } from 'react';
// ✅ 使用 forwardRef 接收并转发 ref
const Audio = forwardRef<HTMLAudioElement, { src: string; loop?: boolean }>(({ src, loop = true }, ref) => {
const handleVolumeChange = (value: number) => {
if (ref && ref.current) {
ref.current.volume = value / 100;
}
};
return (
<div>
<audio ref={ref} loop={loop} controls>
<source src={src} type="audio/mpeg" />
Your browser does not support the audio element.
</audio>
<input
type="range"
min="0"
max="100"
defaultValue="100"
onChange={(e) => handleVolumeChange(Number(e.target.value))}
/>
<span> Volume: {ref?.current?.volume?.toFixed(2) ?? '—'}</span>
</div>
);
});
export const App = ({ name }: { name: string }) => {
const audios = [
{ src: '/fire.mp3' },
{ src: '/crickets.mp3' }
];
// ✅ 为每个音频创建独立 ref,并存于 useRef 中(避免重渲染时重建)
const audioRefs = useRef<(React.RefObject<HTMLAudioElement>)[]>([]);
audioRefs.current = audioRefs.current.length === 0
? audios.map(() => createRef<HTMLAudioElement>())
: audioRefs.current;
return (
<div>
<h2>{name}'s Audio Player</h2>
{audios.map((audio, index) => (
// ✅ 直接展开 audio 属性,并将对应 ref 传入 forwardRef 组件
<Audio
key={audio.src}
src={audio.src}
ref={audioRefs.current[index]}
/>
))}
</div>
);
};? 关键要点说明:
-
forwardRef的第二个参数ref是 React 专门提供的 ref 容器,必须显式绑定到目标 DOM 节点(如<audio ref="{ref}"></audio>),不可试图从props中解构; -
useRef([...]).current在渲染中直接访问是安全的,但需确保数组长度与map数量一致(推荐在useEffect或惰性初始化中构建,或如示例中用条件赋值避免重复创建); - 不要将
ref与其他数据混在同一个对象里再整体传入——这违背了 ref 的设计语义,也破坏了类型安全与可维护性; - 若需批量控制(如统一播放/暂停),可封装
useImperativeHandle暴露方法,但本例中直接操作ref.current已足够。
通过 forwardRef,你既保持了组件的封装性,又精准获得了对底层 <audio></audio> 元素的控制权——这是 React 官方推荐、类型安全且符合 Hooks 规范的标准实践。


















