React.js hooks 练习案例-网易云音乐【day4】

成果展示

昨天又玩了一下,把播放歌曲功能做了,现在可以播放歌曲,导航条会跟随歌曲时间前进,也可以拖拽进度条跳到某个节点继续播放,虽然这样对普通开发没有打作用,但可以提高前端对js的控制能力和数据的处理能力。通过这个项目我也多多少少在开发上了点新的认识,希望你们一样。
在这里插入图片描述

/* 导入组件 */
import React, { memo, useState, useEffect, useRef, useCallback } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { NavLink } from 'react-router-dom';
import { Slider } from 'antd';
/* 导入功能 */
import { getSongDetailAction } from '../store/actionCreators';
import { getSizeImage, formatDate, getPlaySong } from '@/utils/format-utils';
/* 导入UI */
import { PlaybarWrapper, Control, PlayInfo, Operator } from './style';

/* 组件:底部播放条 */
export default memo(function RTAppPlayBar() {
  
  /* 组件 state 模块 */
  // hooks:useState 缓存数据 
  const [currentTime, setCurrentTime] = useState(0); // 当前播放时间
  const [progress, setProgress] = useState(0); // 进度条当前位置
  const [isChanging, setIsChanging] = useState(false); // 进度条是否被拖拽
  const [isPlaying, setIsPlaying] = useState(false); // 进度条是否被拖拽

  /* 共享 state 模块 */
  // 使用 redux-hooks:useSelector 获取共享数据-当前播放歌曲
  const { currentSong } = useSelector(state => ({
    currentSong: state.getIn(["player", "currentSong"])
  }), shallowEqual); // 共享数据浅对比,改变则重绘

   /* 默认参数模块 */
   const picUrl = (currentSong.al && currentSong.al.picUrl) || ""; // 歌手图像url
   const singerName = (currentSong.ar && currentSong.ar[0].name) || "未知歌手"; // 歌手名称
   const duration = currentSong.dt || 0; // 歌曲总时长(秒)
   const showDuration = formatDate(duration, "mm:ss"); // 显示歌曲总时长
   const showCurrentTime = formatDate(currentTime, "mm:ss"); // 显示当前播放时间

  /* 功能模块 */
  // 使用 redux-hooks:useDispatch 获取 dispatch
  const dispatch = useDispatch();

  // 使用 hooks:useEffect 函数挂载后执行
  // 获取播放歌曲明细请求接口
  useEffect(() => {
    dispatch(getSongDetailAction(167876))
  }, [dispatch])

  // 根据id请求歌曲mp3文件
  useEffect(() => {
    audioRef.current.src = getPlaySong(currentSong.id);
  }, [currentSong])

  // 操作播放器 DOM
  const audioRef = useRef(); 

  // 播放功能
  const playMpusic = useCallback(() => {
    isPlaying ? audioRef.current.pause() : audioRef.current.play(); // true:播放 false:暂停
    setIsPlaying(!isPlaying);
  },[isPlaying])

  // 时间更新功能
  const tiemUpdate = (e) => {
    if (!isChanging) { // 进度条没被拖拽时更新
      setCurrentTime(e.target.currentTime * 1000); // 设置当前播放时间
      setProgress(currentTime / duration * 100); // 当进度条没有被拖动时更新进度条当前位置
    }
  }

  // 当函数传入子组件时,父组件重绘函数也会被重新生成,新的函数与旧函数地址不一致,子组件也会触发重绘。
  // 使用 hooks:useCallback 包裹,会将函数保存到缓存,父组件重绘后返回同一个函数,则不会引发子组件重绘提高性能
  // 拖动进度条改变事件
  const sliderChange = useCallback((value) => {
    setIsChanging(true); // 设置进度条正在被拖拽
    const currentTime = value / 100 * duration / 1000; // 计算当前播放时间
    setCurrentTime(currentTime * 1000); // 设置当前播放时间
    setProgress(value); // 设置进度条当前位置
  }, [duration]); // 歌曲总时长改变则重绘

  // 鼠标放开进度条改变事件
  const sliderAfterChange = useCallback((value) => {
    const currentTime = value / 100 * duration / 1000; // 计算当前播放时间
    setCurrentTime(currentTime * 1000); // 设置当前播放时间
    audioRef.current.currentTime = currentTime; // 设置进度条当前位置与歌曲播放位置
    setIsChanging(false); // 设置进度条没被拖拽
    if(!isPlaying) playMpusic();
  }, [duration, isPlaying, playMpusic]); // 歌曲总时长改变则重绘


  /* UI布局模块 */
  return (
    <PlaybarWrapper className="sprite_player">
      <div className="content wrap-v2">
        <Control isPlaying={isPlaying}>
          <button className="sprite_player prev"></button>
          <button className="sprite_player play" onClick={e => playMpusic()}></button>
          <button className="sprite_player next"></button>
        </Control>
        <PlayInfo>
          <div className="image">
            <NavLink to="/discover/player" >
              <img src={getSizeImage(picUrl, 35)} alt="" />
            </NavLink>
          </div>
          <div className="info">
            <div className="song">
              <span className="song-name">{currentSong.name}</span>
              <a href="/todo" className="singer-name">{singerName}</a>
            </div>
            <div className="progress">
              <Slider defaultValue={30}
                value={progress}
                onChange={sliderChange}
                onAfterChange={sliderAfterChange} />
              <div className="time">
                <span className="now-time">{showCurrentTime}</span>
                <span className="divider">/</span>
                <span className="duration">{showDuration}</span>
              </div>
            </div>
          </div>
        </PlayInfo>
        <Operator>
          <div className="left">
            <button className="sprite_player btn favor"></button>
            <button className="sprite_player btn share"></button>
          </div>
          <div className="right sprite_player">
            <button className="sprite_player btn volume"></button>
            <button className="sprite_player btn loop"></button>
            <button className="sprite_player btn playlist"></button>
          </div>
        </Operator>
      </div>
      <audio ref={audioRef} onTimeUpdate={tiemUpdate} />
    </PlaybarWrapper>
  )
})
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值