今晚不熬夜_ 发表于 2021-5-30 09:00

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

本帖最后由 今晚不熬夜_ 于 2021-5-30 09:52 编辑

## 前言

### 前言

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


![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/20201108130517.gif)



### 项目预览和源码

- 在线预览地址👉:[点我跳转到云音乐](http://www.wanguancs.top)
- 项目`Gihub`地址👉:(https://github.com/wanguano/Music163-React) (如果觉得项目还不错的话 👏,就给个 star ⭐ 鼓励一下吧~)
- 没有外网的小伙伴👉:(https://gitee.com/xmkm/Music163-React)



### 最近更新

#### 更新功能

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

#### *修改BUG&ToDoList*

点击查看👉[近期优化调整](https://github.com/wanguano/Music163-React#%E5%BE%85%E4%BC%98%E5%8C%96)

点击查看👉(https://github.com/wanguano/Music163-React#to-do-list%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%BC%80%E5%8F%91)

### 界面功能展示(新开发)

#### 歌曲搜索(↑↓选择)

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/new-search.gif)

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

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/20210315165207.gif)

#### 登录演示

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/go-login.gif)

#### 每日推荐

!(https://gitee.com/xmkm/cloudPic/raw/master/img/image-20210530083650349.png)

#### 个人主页

!(https://gitee.com/xmkm/cloudPic/raw/master/img/image-20210530083718485.png)







## 音乐列表

> 下面开始完成本节(稍复杂),歌曲列表播放组件,支持功能如下:
>
> - 支持点击某一项播放歌曲
> - 歌词实时滚动
> - 和首页的歌词组件互斥效果
> - 清除全部歌曲
> - 拖拽更改顺序

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/image-20201019223742622.png)

### 1.页面布局搭建

1. 页面布局搭建

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/image-20201019135533953.png)

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

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/show-playlist.gif)

3. 和歌词展示控件互斥.(歌词列表显示,关闭歌词展示)

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/isShow-lyric.gif)


4. Conten内容搭建

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/image-20201019214750518.png)


5. 实现点击一项,播放对应的音乐,并且背景高亮

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

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

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

   - 对应`item`背景高亮跟着切换 (根据保存在`redux`中的`currentSongIndex`)

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/switch-music.gif)

6. 实现点击删除按钮,阻止事件冒泡和清除点击播放歌曲

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

![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/563211c1b28f4a338ab9d180729f40b4~tplv-k3u1fbpfcp-watermark.image" alt="delete-all-or-single-music.gif)



### 2.歌词高亮效果

#### 歌词接口

- 请求接口:
- http://39.102.36.212:3000/lyric?id=歌曲ID
- http://39.102.36.212:3000/lyric?id=167876
- 对请求下来的歌词进行解析
- 在上一节咱们已经存放到`redux`当中,参考👉[歌词格式化](https://juejin.cn/post/6914167815469531143#heading-41)

- 实现当前播放的歌词高亮显示,获取当前是否播放状态滚动歌词



### 3.歌词滚动效果

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

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/result.gif)





## 音乐列表拖拽排序sortablejs

### 说明

- <font color='red'>**当需要对某些表格的行或者列进行拖拽排序时**</font>
- 并不局限表格
- 只要是你想拖拽的某一行都可以



### 使用拖拽排序步骤

1. 安装

```powershell
npm install sortablejs --save
yarn add sortablejs
```

2. 导入

```js
import Sortable from 'sortablejs';
```

3. 配置参数

```js
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));
      // 更改播放列表顺序
      dispatch(changePlayListAction(tempPlayList))
    },
});
}, );
```

4. 完成效果

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/20210315165207.gif)





## 本地存储音乐列表

> 考虑`immutable`和`redux-persise`相互不能兼容,主要原因是`immutable`的数据格式,`redux-persist`不认识,导致我们想把`redux`中保存的状态不能进行本地存储,尝试了`redux-persist-transform-immutable`进行转换还是不行,可能使用的姿势不对,有使用过的大佬请指导一下,搞了两三次,最后只能撤销代码,自己还是太菜了,哭了&#128549;
>
> 那就换一种思路,手动的只将**歌曲列表进行本地存储(只存储歌曲id)**,将歌曲歌曲列表中所有歌曲`id`保存到`local Storage`中

### 默认歌曲列表

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

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/20210402212633.png)

### 实现效果

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/refresh-storage.gif)

### 歌曲顺序(异步问题)

> 一个小问题:在我们遍历歌曲列表数组`id`发送网络请求时,由于发送网络请求是异步的,导致我们的音乐列表的歌曲顺序并不是按照发送数组设定好的顺序,有没有一种办法可以**只有上一次网络请求成功之后再进行发送这次网络请求呢?**
>
> 答案是肯定的,首先想到`promise`的小伙伴加鸡腿&#127831;,当然也可以使用`async await`

#### 思路

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

#### 代码

```javascript
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
      new Promise((resolve, reject) => {
      excuteRun &&
          getSongDetail(idx).then((res) => {
            excuteRun = false
            // console.log(res.songs)
            // (0)歌曲ID添加到本地存储
            addPlaylistId(idx)
            const song = res.songs && res.songs
            // 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`数组顺序进行请求了

![](https://cdn.jsdelivr.net/gh/wanguano/cloudPic/img/refresh-index.gif)

#### 以优化

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

> 到现在我们已经完成「网易云音乐PC」基本功能,相信你对`React`全家桶已经是比较熟练了,接下来想往哪方面扩展可以自行补充完善功能;
>
> 如果文章中有哪部分不明白的或写的不好或是有什么建议欢迎大家提出&#129303;,希望和大家共同进步;



## 感谢

- 非常感谢[王红元](https://github.com/coderwhy)老师的**React核心技术实战**让我学习到很多 react 的知识。
- 非常感谢后台提供者(https://github.com/Binaryify/NeteaseCloudMusicApi),接口很稳定,文档很完善

asdime 发表于 2021-5-30 11:57

前端大佬啊,学习了

indiou 发表于 2021-6-5 04:06

最近正在学习React,楼主能推荐一下有哪些好的项目吗?其实就是愁接口,有些接口用起来真费劲

leng199310 发表于 2021-5-30 09:37

来领cb……

_知鱼之乐 发表于 2021-5-30 09:44

支持一下

ntwmkpx 发表于 2021-5-30 09:48

这个真不错,过来学习下,感谢楼主分享啊。

cullenlin123 发表于 2021-5-30 09:49

果然我是看不懂的。最近都下不了音乐了

ivan132 发表于 2021-5-30 09:50

牛叉啊,感谢分享

QingYi. 发表于 2021-5-30 11:49

做這個花了多長時間了

小卡爱熟妇 发表于 2021-5-30 11:54

厉害了!学习学习

sympohh 发表于 2021-5-30 12:21

大佬nb,只存储歌曲id厉害了
页: [1] 2 3 4 5 6
查看完整版本: 基于React全家桶开发「网易云音乐PC」项目实战(四)