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

TypeScript错误:audioPlayer.current可能为null的解决求助

解决React音频播放器TypeScript中"audioPlayer.current may be null"错误

关键修复步骤

  • 为Ref添加明确类型标注
    TypeScript需要知道ref关联的DOM元素具体类型,给每个useRef指定对应类型:

    • audioPlayer对应HTML音频元素:const audioPlayer = useRef<HTMLAudioElement | null>(null);
    • progressBar对应Range输入框:const progressBar = useRef<HTMLInputElement | null>(null);
    • animationRef存储动画ID:const animationRef = useRef<number | null>(null);
  • 访问Ref前必须做空值检查
    所有使用ref.current的场景,先确认它不为null,可通过可选链?.或if判断实现,避免TS报错:

    • 在useEffect中先校验ref存在,再读取音频时长:
      useEffect(() => {
        if (audioPlayer.current && progressBar.current) {
          const seconds = Math.floor(audioPlayer.current.duration);
          if (!isNaN(seconds)) {
            setDuration(seconds);
            progressBar.current.max = seconds.toString();
          }
        }
      }, [audioPlayer.current?.readyState]);
      
    • 播放/暂停、进度更新等函数中,同样添加空值校验,比如togglePlayPause:
      const togglePlayPause = () => {
        const prevValue = isPlaying;
        setIsPLaying(!prevValue);
        if (!prevValue && audioPlayer.current) {
          audioPlayer.current.play();
          animationRef.current = requestAnimationFrame(whilePlaying);
        } else if (audioPlayer.current) {
          audioPlayer.current.pause();
          if (animationRef.current) {
            cancelAnimationFrame(animationRef.current);
          }
        }
      };
      
  • 完善State和函数参数的类型标注
    明确变量类型让TS类型检查更精准:

    • currentTrack类型为string | null:const [currentTrack, setCurrentTrack] = useState<string | null>(null);
    • calculateTime参数指定为number:const calculateTime = (secs: number) => { ... }
    • 事件参数添加类型:const changeTrack = (e: React.MouseEvent<HTMLInputElement>) => { ... }

修复后完整代码

import React, { useState, useRef, useEffect } from "react";

const AudioPlayer = () => {
  // state
  const [isPlaying, setIsPLaying] = useState(false);
  const [duration, setDuration] = useState(0);
  const [currentTime, setCurrentTime] = useState(0);
  const [currentTrack, setCurrentTrack] = useState<string | null>(null);

  // references - 添加明确的类型标注
  const audioPlayer = useRef<HTMLAudioElement | null>(null);
  const progressBar = useRef<HTMLInputElement | null>(null);
  const animationRef = useRef<number | null>(null);

  useEffect(() => {
    // 先检查ref是否存在
    if (audioPlayer.current && progressBar.current) {
      const seconds = Math.floor(audioPlayer.current.duration);
      // 避免NaN的情况
      if (!isNaN(seconds)) {
        setDuration(seconds);
        progressBar.current.max = seconds.toString();
      }
    }
  }, [audioPlayer.current?.readyState]);

  const togglePlayPause = () => {
    const prevValue = isPlaying;
    setIsPLaying(!prevValue);
    if (!prevValue && audioPlayer.current) {
      audioPlayer.current.play();
      animationRef.current = requestAnimationFrame(whilePlaying);
    } else if (audioPlayer.current) {
      audioPlayer.current.pause();
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    }
  };

  const whilePlaying = () => {
    if (audioPlayer.current && progressBar.current) {
      progressBar.current.value = audioPlayer.current.currentTime.toString();
      setCurrentTime(audioPlayer.current.currentTime);
      animationRef.current = requestAnimationFrame(whilePlaying);
    }
  };

  // 为参数添加类型标注
  const calculateTime = (secs: number) => {
    const minutes = Math.floor(secs / 60);
    const returnedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
    const seconds = Math.floor(secs % 60);
    const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
    return `${returnedMinutes} : ${returnedSeconds}`;
  };

  const changeRange = () => {
    if (audioPlayer.current && progressBar.current) {
      const newTime = parseFloat(progressBar.current.value);
      audioPlayer.current.currentTime = newTime;
      setCurrentTime(newTime);
    }
  };

  // 为事件参数添加类型标注
  const changeTrack = (e: React.MouseEvent<HTMLInputElement>) => {
    setCurrentTrack(e.target.value);
    togglePlayPause();
  };

  return (
    <>
      <div className="relative flex justify-center my-10 mx-4">
        <img src="/sphere_3D.jpg" alt="sph" width="600" />
        <p className="huit absolute">8</p>
        <input
          className="dots top-40"
          value="/piste1.mp3"
          onClick={changeTrack}
        />
        <input
          className="dots top-20 left-2/3"
          value="/piste2.mp3"
          onClick={changeTrack}
        />
      </div>
      <div>
        <audio
          ref={audioPlayer}
          src={currentTrack}
          preload="metadata"
          onCanPlay={(e) => e.target.play()}
        />
        <button className="mx-5" onClick={togglePlayPause}>
          {isPlaying ? "Pause" : "Play"}
        </button>

        {/* Current time */}
        <div>{calculateTime(currentTime)}</div>

        {/* progress bar */}
        <div>
          <input
            type="range"
            defaultValue="0"
            ref={progressBar}
            onChange={changeRange}
          />
        </div>

        {/* duration */}
        <div>{duration && !isNaN(duration) && calculateTime(duration)}</div>
      </div>
    </>
  );
};

export default AudioPlayer;

内容的提问来源于stack exchange,提问作者Rom-888

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 21:00:58