阅读 2021
基于React全家桶开发「网易云音乐PC」项目实战(四)

基于React全家桶开发「网易云音乐PC」项目实战(四)

前言

前言

hello大家好我是「风不识途」,如果首次阅读本系列请点击,正在学习React的小伙伴可以克隆该项目,参考学习,尝试做一些小功能,下面我们开始完成本系列最重要的音乐播放器列表▶需要完成内容如下↓;

下拉查看

项目预览和源码

最近更新

更新功能

  • 登录功能:
    • 暂时只支持“163邮箱”或“手机号”登录
    • 每日推荐歌单(只有登录成功才能查看)
    • 个人主页 & 个人收藏歌单 & 评论歌曲 & 点赞歌曲评论 & 创建歌单
  • 本地存储歌曲列表:
    • 不管之后是否刷新浏览器,只要在歌曲列表中就会持久化存储
    • (刷新浏览器,歌曲列表依然存在)
  • 歌曲列表:
    • 对歌曲列表支持拖拽排序,并会对播放顺序进行改变
  • 搜索音乐框:
    • 优化在搜索歌曲时,支持键盘"↑"+"↓"来切换搜索歌曲内容
  • 头部进度条:
    • 在页面路由跳转&网络请求时"添加头部进度条"显示
  • 404页:
    • 添加404页,在路由没有匹配的页面时,会显示404页面

修改BUG&ToDoList

点击查看👉近期优化调整

点击查看👉TO-DO-LIST

界面功能展示(新开发)

歌曲搜索(↑↓选择)

支持对歌曲列表进行拖拽排序

登录演示

每日推荐

image-20210530083650349

个人主页

image-20210530083718485

音乐列表

下面开始完成本节(稍复杂),歌曲列表播放组件,支持功能如下:

  • 支持点击某一项播放歌曲
  • 歌词实时滚动
  • 和首页的歌词组件互斥效果
  • 清除全部歌曲
  • 拖拽更改顺序

1.页面布局搭建

  1. 页面布局搭建

    • 下拉查看
  2. 点击按钮显示和隐藏(播放列表) 和 添加过渡效果

    • 下拉查看 show-playlist
  3. 和歌词展示控件互斥.(歌词列表显示,关闭歌词展示)

    • 下拉查看 isShow-lyric
  4. Conten内容搭建

    • 下拉查看 image-20201019214750518
  5. 实现点击一项,播放对应的音乐,并且背景高亮

    • 在点击当前项后,将id传递函数,根据id派发action,请求播放列表详情信息

    • 将播放音乐函数传递给子组件

    • 实现当音乐播放结束或手动点击上一首或后下一首:

      • 对应item背景高亮跟着切换 (根据保存在redux中的currentSongIndex)
    • 实现效果 switch-music
  6. 实现点击删除按钮,阻止事件冒泡和清除点击播放歌曲

  7. 点击清除全部按钮,清除全部音乐,并播放下一首音乐

    • 实现效果 delete-all-or-single-music.gif

2.歌词高亮效果

歌词接口

3.歌词滚动效果

  • 根据当前播放歌词的 index,实现滚动效果
  • 获取DOM,计算scrollTop,实现滚动
result

音乐列表拖拽排序sortablejs

说明

  • 当需要对某些表格的行或者列进行拖拽排序时
    • 并不局限表格
    • 只要是你想拖拽的某一行都可以

使用拖拽排序步骤

  1. 安装
npm install sortablejs --save
yarn add sortablejs
复制代码
  1. 导入
import Sortable from 'sortablejs';
复制代码
  1. 配置参数
import React, { useRef } from 'react';

// other hook
const playlistRef = useRef();// 用于获取DOM元素

useEffect(() => {
  // 获取列表项父元素
  const el = playlistRef.current.querySelector('.main-playlist');
  new Sortable(el, {
    sort: true,
    animation: 200,
    onEnd: function (evt) {  // 拖拽结束发生该事件
      let tempPlayList = playList
      tempPlayList.splice(evt.newIndex, 0, playList.splice(evt.oldIndex, 1)[0]);
      // 更改播放列表顺序
      dispatch(changePlayListAction(tempPlayList))
    },
  });
}, [playList, dispatch]);
复制代码
  1. 完成效果

本地存储音乐列表

考虑immutableredux-persise相互不能兼容,主要原因是immutable的数据格式,redux-persist不认识,导致我们想把redux中保存的状态不能进行本地存储,尝试了redux-persist-transform-immutable进行转换还是不行,可能使用的姿势不对,有使用过的大佬请指导一下,搞了两三次,最后只能撤销代码,自己还是太菜了,哭了😥

那就换一种思路,手动的只将歌曲列表进行本地存储(只存储歌曲id),将歌曲歌曲列表中所有歌曲id保存到local Storage

默认歌曲列表

在我们每次请求歌曲的时候,其实只需要知道歌曲id就可以了,这样我们只需要将在第一次初始化的的时候将歌曲id进行存储在本地就可以了,不过需要对本地存储的歌曲id进行去重就可以了(重复的歌曲id不进行本地存储)

实现效果

歌曲顺序(异步问题)

一个小问题:在我们遍历歌曲列表数组id发送网络请求时,由于发送网络请求是异步的,导致我们的音乐列表的歌曲顺序并不是按照发送数组设定好的顺序,有没有一种办法可以只有上一次网络请求成功之后再进行发送这次网络请求呢?

答案是肯定的,首先想到promise的小伙伴加鸡腿🍗,当然也可以使用async await

思路

(1)解决方案:promise + setinterval(定时器)
(2)可能有同学会问,为什么使用定时器呢?(不一定使用要使用定时器+promise这种方案)
(3)这是因为在咱们发送ajax时,不能很好的进行控制,使用一个标识变量来进行控制ajax是否发送(默认为true)
(4)在每次开始定时器时,首先判断标识变量是否为true如果为true就发送ajax,
  在本次请求ajax时设置标识变量为false(即在定时器中不会再发送网络请求),在本次ajax完成时(即异步操作成功时),改变标识变量为true
  这样就能进行很好的控制,简单的总结一下:就是必须控制本次ajax发送请求成功时,才能进行下一次ajax
  (核心在于使用标识变量,来控制ajax请求,且只有上次ajax请求成功,才能进行下一次ajax)
复制代码

代码

export const getSongDetailArrayAction = (listId) => {
  return (dispatch, getState) => {
    // 1.获取歌曲列表
    const playList = getState().getIn(['player', 'playList'])
    let i = 0
    let timer = null
    let excuteRun = true // 控制ajax
    timer = setInterval(() => {
      let idx = listId[i]
      new Promise((resolve, reject) => {
        excuteRun &&
          getSongDetail(idx).then((res) => {
            excuteRun = false
            // console.log(res.songs[0])
            // (0)歌曲ID添加到本地存储
            addPlaylistId(idx)
            const song = res.songs && res.songs[0]
            // console.log(song)
            if (!song) return
            // (1)添加到播放列表中
            playList.push(song)
            dispatch(changePlayListAction(playList))
            // (2)更改当前播放的索引
            const songIndex = playList.length - 1
            dispatch(changeSongIndexAction(songIndex))
            // (3)更改当前播放歌曲
            dispatch(changeCurrentSongAction(song))
            // (4)请求歌曲的歌词
            dispatch(getLyricAction(idx))
            // (5)更新歌曲数量
            dispatch(changePlayListCount(playList.length))
            resolve(i)
          })
      }).then((value) => {
        excuteRun = true
      })
      i++
      if (i >= listId.length) {
        clearInterval(timer)
      }
    })
  }
}
复制代码

效果

  • 这下不管刷新多少次,都会按照咱们的本地存储中歌曲列表id数组顺序进行请求了

以优化

  • 支持记忆歌曲列表
    • 刷新页面后,音乐播放列表可以记忆上次播放顺序
  • 记忆在关闭页面前播放的音乐
    • 刷新页面后,关闭页面前记忆当前播放的歌曲,再次打开时默认歌曲是关闭前播放的歌曲

到现在我们已经完成「网易云音乐PC」基本功能,相信你对React全家桶已经是比较熟练了,接下来想往哪方面扩展可以自行补充完善功能;

如果文章中有哪部分不明白的或写的不好或是有什么建议欢迎大家提出🤗,希望和大家共同进步;

感谢

  • 非常感谢王红元老师的React核心技术实战让我学习到很多 react 的知识。
  • 非常感谢后台提供者Binaryify,接口很稳定,文档很完善
文章分类
前端
文章标签