前言
hello大家好,我是"風不識途",很長時間沒有更新了~,很多朋友一直在催更新(其實沒有),本人最近在實習實在有點忙=.=,有時間的話可以将面試&實習經曆總結一下。好了回歸正題,在前面我們已經完成了轉嵌套路由的點選跳轉切換了和輪播圖,如果首次閱讀本系列請點選,下面我們開始完成首頁的主體内容+音樂播放器▶需要完成内容如下↓。

項目預覽和源碼
- 線上預覽位址👉:www.wanguancs.top
- 項目
位址👉: Musci 163 如果覺得項目還不錯的話 👏,就給個 star ⭐ 鼓勵一下吧~Gihub
首頁主體内容
1.熱門推薦頭部元件
Content主體布局
要完成的預覽圖如下↓
header元件封裝「前言」
- 檢視需要封裝的header元件
基于React全家桶開發「網易雲音樂PC」項目實戰(三)
- 為什麼封裝:由于在目前頁面下,有多個類似的的頭部(header)元件
- 在目前頁面中有的
元件是沒有header
關鍵字(keywords
):也就是熱門推薦後面的分類
- 點選檢視多個
元件 差異header
基于React全家桶開發「網易雲音樂PC」項目實戰(三)
"頭部(header)元件"封裝
- 元件存放路徑(參考):将
封裝到header
->src
->components
檔案夾下ThemeHeader
- 元件需要依賴傳遞的
:props
- 元件的
,必傳;title(标題)
-
(可以先寫死資料),非必傳;keyword(關鍵字分類)
- 元件的
- 使用
傳遞預設值propTypes
// theme-header-rcm.js
import propTypes from 'prop-types'
// ...
// 指定傳遞props
ThemeHeaderRmc.propTypes = {
// title屬性必填(左側标題)
title: propTypes.string.isRequired,
// 關鍵字(非必傳,左側關鍵字)
keywords: propTypes.array
}
// 指定預設值
ThemeHeaderRmc.defaultProps = {
keywords: []
}
實作效果
RecommendWrapper首頁外層結構劃分(參考)
2.熱門推薦子產品->發送網絡請求
- 這個時候我們就可以來發送網絡請求,将請求下來的資料儲存在
中redux的store
- 熱門推薦API接口↓:
- /personalized?limit=8
- 示例:http://123.57.176.198:3000/personalized?limit=8
一個元件需要發送網絡的基本步驟
// 1.在網絡請求對應檔案中封裝對應的函數
// 2.修改"目前"元件目錄下store中的reducer (前提在actionTypes定義常量名)
// 3.在actionCreator檔案中添加action發送網絡請求
// 4.在元件中使用dispatch action 測試網絡請求資料
// 5(可選).定義常量 constans 用于控制limit的數量(友善後期維護修改)
// 6.在元件中使用useSelector展示資料
詳細步驟如下👇
- 網絡請求接口的封裝: src/service/recommend.js
export function getHotRecommends(limit) { return request({ url: "/personalized", params: { limit } })
- 修改redux
基于React全家桶開發「網易雲音樂PC」項目實戰(三) - 添加actionCreator
基于React全家桶開發「網易雲音樂PC」項目實戰(三) - 定義常量用于控制
的數量, 好處是如果有一天想修改數量直接在常量檔案中修改即可limit
- 在元件中使用useSelector展示資料
基于React全家桶開發「網易雲音樂PC」項目實戰(三)
3.歌曲封面(song cover)元件封裝
- 公共元件位置: src/components/song-cover/index.js
點選檢視 歌曲封面(song cover)元件封裝:
接口字段:
封面圖檔: picUrl
播放數量: playCount
封面名字: name
封面底部文字: copywriter
song cover元件布局思路👇👇👇
- 将儲存在
中熱門推薦的八條資料,進行周遊(外層包裹div, 并使用flex布局, flex-wrap換行)redux
4.熱門推薦元件完成效果
5.新碟上架
網絡請求
- 新碟上架接口:
- /top/album?limit=10 已廢除
- /album/newest
- 示例:http://123.57.176.198:3000/album/newest
- 将請求的資料放到
裡面reudx
- 封裝網絡接口請求
- 修改
,添加redux
,添加state
語句case
- 添加
:actionCreator
getNewAlbumsAction
- 用于發送網絡請求
- 元件派發該
測試action
- 添加
:actionCreator
changeNewAlbumAction
- 用于在網絡請求中派發該
action
- 用于在網絡請求中派發該
state
基于React全家桶開發「網易雲音樂PC」項目實戰(三)
新碟上架元件布局
- 輪播圖: 使用
(走馬燈)控件Carousel
基于React全家桶開發「網易雲音樂PC」項目實戰(三)
資料截取邏輯代碼
//1.資料方面:為了保證輪播圖的每1個頁有5條資料:
// 對資料進行截取: 在周遊第一頁中0-5 第二頁5-10資料
// 注意:slice(方法不包括截取目标number)
// 2.布局方面:
// 對class name為npage及進行flex布局
<Carousel dots={false} ref={albumRef}>
{[0, 1].map(item => {
return (
<div key={item} className="page">
{newAlbums.slice(item * 5, (item + 1) * 5).map(cItem => {
return (
<div key={cItem.id} className="c-item">
{cItem.name}
</div>
)
})}
</div>
)
})}
</Carousel>
- 待完善效果🤔
AlbumCover元件封裝
// AlbumCover元件要求(資料是不固定的): 寬高和bgp(背景圖檔橫縱坐标)由調用者傳遞
// 因為在其他的頁面中使用該元件的尺寸和bgp是不同的
<Carousel dots={false} ref={albumRef}>
...
<AlbumCover
key={cItem.id}
info={cItem}
size={100}
bgp="-570px"
>
{cItem.name}
</AlbumCover>
...
</Carousel>
- 完成效果😏
6.榜單
榜單說明
請求榜單資料
- 榜單資料API: /top/list?idx=0 已廢棄,項目接口又重新改了一遍
- 0: 雲音樂飙升榜
- 2: 雲音樂新歌榜
- 3: 雲原創歌曲榜
- 榜單資料API:
- 下拉檢視: 首頁三個榜單的 id
基于React全家桶開發「網易雲音樂PC」項目實戰(三) - 雲音樂飙升榜API: http://localhost:3000/playlist/detail?id=19723756
- 雲音樂新歌榜API: http://localhost:3000/playlist/detail?id=3779629
- 網易原創歐曲榜API: http://localhost:3000/playlist/detail?id=2884035
- 在請求下來的資料,對其中的
字段進行資料截取,隻保留前10條資料tracks
- 下拉檢視: 首頁三個榜單的 id
- 注意事項↓
發送網絡請求将請求的資料放到redux中state中 (詳細步驟不在展開,和上面步驟一樣)
注意: 根據不同 id 請求不同榜單
在派發action時可以使用switch根據不同的 id 派發不同的action
榜單元件(top-ranking)封裝
- 要完成的元件封裝如下👇
- 要實作效果
- 剛開始:
的父元素固定的icons
為0,width
後給固定的寬度hover
- 滑鼠劃過
讓文字溢出隐藏顯示…,并顯示這行item
icons
- 滑鼠離開顯示原本效果,隐藏
,固定歌曲名字寬度即可icons
- 剛開始:
完成效果
7.主體右側
暫時不做登入具體功能,先隻做資料渲染。登入子產品布局比較簡單就略過了;
入駐歌手(settle-singer)
- 入駐歌手
API:
-
/artist/list?limit=5&cat=5001
- 示例:
http://123.57.176.198:3000/artist/list?limit=5&cat=5001
-
- 傳回的
如下JSON
{
picUrl(pin):"http://p4.music.126.net/LCWqYYKoCEZKuAC3S3lIeg==/109951165034938865.jpg"
followed(pin):false
briefDesc(pin):""
name(pin):"薛之謙"
id(pin):5781
alias(pin):
musicSize(pin):275
accountId(pin):97137413
picId_str(pin):"109951165034938865"
img1v1Id_str(pin):"109951165034950656"
}
熱門主播
- hot-artist
- 接口沒找到,那就先寫死吧,在:src/common/local-data.js 檔案已經寫好了
- 傳回的
如下JSON
{
picUrl: 'http://p1.music.126.net/H3QxWdf0eUiwmhJvA4vrMQ==/1407374893913311.jpg',
name: '陳立',
position: '心理學家、美食家陳立教授',
url: '/user/home?id=278438485',
},
音樂播放
音樂播放元件(app-play-bar)
播放器元件說明:我們在網易雲音樂官網切換頁面時,會發現音樂播放一直是固定在下面的,和路由切換沒有關系
- 元件存放位置: 是以我們将
元件封裝到app-play-bar
檔案夾📂中src/psges
布局參考
PlayBar元件布局采用固定定位:
PlayerWrapper↓
内容(content)分了三個部分:↓
Control(左側)
三個按鈕.添加背景圖,外層采用flex布局
PlayIInfo(中間)
兩個部分(上、下),下面滑動條采用andt元件庫Slider元件,找到類名覆寫樣式即可
Opertaor(右側)
兩部分,外層采用flex布局
Slider元件(進度條)樣式覆寫
Slider元件樣式更改(覆寫)
1.外層包裹背景圖和寬高樣式..
2.mairign邊距為0
3.設定背景顔色為透明
4.設定滑鼠滑動時的背景圖
5.設定圓點的樣式覆寫
完成效果
- 圖檔和一些動态擷取的資料暫時先寫死
歌曲播放資料請求
- 先固定播放一首歌曲
- 歌曲API接口:/song/detail?ids=167876
- 示例:http://123.57.176.198:3000/song/detail?ids=167876
- 請求下來的資料放在哪呢?
- 請求下的由于是歌曲資訊是以就放在
檔案夾下player
檔案夾📂下的store
中進行儲存store
-
中使用rducer
管理stateimmutable
- 在項目根目錄,導入
檔案夾下的player
,進行reducer
(合并)combine
- 請求下的由于是歌曲資訊是以就放在
step1
添加player中reducer預設state的 currentSong: {}
發送網絡請求: player.js
将資料儲存在reducer中
step2
在plaer元件使用redux中請求來的資料:
useSelector
更改player(播放器)元件的固定資料
currentSong.al.picUrl 圖檔
currentSong.name 歌曲名字
currentSong.ar[0].name 作者
currentSong.dt (歌曲總時長,格式化)
導入時間格式化工具(轉換時間格式)
formatDate(duration, "mm:ss")
播放音樂功能
- 歌曲播放
接口: https://music.163.com/song/media/outer/url?id=${id}.mp3API
-
是動态的:可以從請求下來id
擷取目前歌曲目前歌曲(currentSong)
資訊id
-
音樂播放邏輯
下面我們開始做音樂播放功能
step1
添加 audio 标簽
點選 ▶播放按鈕 監聽click事件,添加src屬性↓
step2 音樂播放
使用useRef,擷取audio的dom元素
封裝: 歌曲播放`API`接口,id作為參數
之後點選 播放▶按鈕 動态設定scr屬性,調用play方法開始播放
step3 歌曲時間顯示
建立元件局部狀态: currentTime 用于更改目前播放時間
audio元素有一個OnTimeUpdate事件,當歌曲事件發生變化就會被回調
事件參數: e.currentTime屬性(用于擷取目前播放時間)
對秒數->對時間進行格式化->formatDate(currentTime, 'mm:ss')
step4 進度條滾動
控制andt的Slider元件的value值: 目前播放的進度=目前時間/總時長*100
拖動滑塊邏輯
下面我們開始做拖動滑塊,播放對應的進度歌曲
需求:
Slider元件滑動時: 目前時間會發生改變
Slider元件擡起時: 目前歌曲進度發生改變
step1
1.Slider元件提供了2個api:
(1)onChange: 當滑塊被拉動時觸發,函數的參數是拖動的value
(2)onAfterChange: 當滑擡起時觸發,函數的參數是擡起時的value
2.progress狀态用于儲存目前Slider進度,當歌曲播放觸發更改進度
(1)onChange事件參數的value為目前滑動的進度值: 更改setProgress進度
(2)當我們播放音樂時,拖動滑塊時,會有bug(進度條被拉到前面了)
(2.1)原因: 這是因為我們在onChange事件中,和timeUpdate事件中都在更改"progress"進度值,在歌曲播放時觸發TimeUpdate事件中也更改了"progress"進度
(2.2)解決: 元件中建立一個用于辨別是否正在改變的state,如果不是在change那麼就在歌曲播放事件中更改progress進度,最後在擡起事件中再将辨別change變量更改為false
step2
1.設定歌曲的src屬性,放到uesEffect當中依賴于currentSong
2.播放暫停功能,背景圖切換
FAQ
progress進度: 1-100
currentTime: 要的是毫秒數
audioRef.current.currentTime: 要的是總秒數
歌曲播放具體功能完善
點選頁面上的一首歌播放音樂
- 音樂播放邏輯
- 1.在
中添加需要的字段reducer
-
記錄目前播放音樂的索引currentSongIndex
-
播放清單playList
-
- 2.請求歌曲詳情邏輯
- 下拉檢視
基于React全家桶開發「網易雲音樂PC」項目實戰(三)
- 下拉檢視
- 3.當一個元件内部的
被其他元件使用時(參考)actionCreator
// 将添加歌曲action導出 export { reducer, actionCreator }
- 完成效果
- 下拉檢視
基于React全家桶開發「網易雲音樂PC」項目實戰(三)
- 下拉檢視
單曲循環或順序播放或清單循環
- 目前歌曲播放完畢後,決定下一首是順序播放還是單曲循環等等
第一種思路: 建立一個播放清單數組,決定下一首播放什麼音樂
如果是順序播放,直接把源數組拷貝,如果是随機播放,将順序打亂
第二種思路: 決定下一首播放什麼音樂,讓目前currentSongIndex + 1,
設計順序的資料結構(sequence)
0 順序播放
1 随機播放
2 單曲循環
背景圖切換
歌曲清單顯示個數
點選按鈕播放上一首或下一首
- 點選按鈕: 播放上一首或下一首音樂
- 兩個按鈕監聽點選事件: 都使用同一個函數,傳遞不同的tab(标記),處理不同的邏輯
- 因為需要派發action,是以放到actionCreator裡編寫
- 單曲循環也是切換到下一首的, 是以它們的邏輯一樣
- 切換歌曲的實作思路(參考):
- 根據
決定是順序播放還是随機播放playSequence
- 根據播放順序選擇下一首音樂
- 随機播放 …
- 順序播放 …
- 擷取需要播放的音樂
- 更改目前播放的索引
- 更改目前播放的音樂
- 根據
決定下一首音樂播放的順序
- 給音頻元素監聽:
事件(歌曲播放完後觸發)onEnded
- 目前歌曲播放完後隻有兩種情況:
- 第一種情況: 單曲循環
- 設定目前播放時間為0:
audioRef.current.currentTime = 0
- 設定目前播放時間為0:
- 第二種情況: 切換下一首音樂(根據
決定是随機播放還是順序播放)playSequence
- 第一種情況: 單曲循環
其他細節補充
- 點選切換歌曲順序圖示按鈕後: 切換對應圖示
0順序播放
1随機播放
2單曲循環
-
修改狀态時為什麼要添加随機值?setIsPlaying
- 如果目前是播放狀态: 添加下一首音樂時, 還是播放狀态, 設定的值還是
true
- 如果這一次的值和上一次的值時相同的, 就不會執行依賴于
的isPlaying
回調useEffect
- 是以每次更新
時, 需要顯示的更新isPlaying
isPlaying
- 如果目前是播放狀态: 添加下一首音樂時, 還是播放狀态, 設定的值還是
- 點選切換順序按鈕後, 懸浮目前播放的順序文本提示, 單曲循環還是随機播放等等
- Tooltip文字提示元件, 滑鼠經過顯示氣泡, 内容是單曲循環還是随機播放等等
歌詞顯示
對請求下來的歌詞分析
歌詞資料
API
接口
- http://123.57.176.198:3000/lyric?id=167876
[00:00.000] 作曲 : 許嵩 -> {time: 毫秒, content: "歌詞内容"}
[00:01.000] 作詞 : 許嵩
[00:22.240]天空好想下雨
[00:24.380]我好想住你隔壁
[00:26.810]傻站在你家樓下
[00:29.500]擡起頭數烏雲
[00:31.160]如果場景裡出現一架鋼琴
[00:33.640]我會唱歌給你聽\n[00:35.900]哪怕好多盆水往下淋\n[00:41.060]夏天快要過去\n[00:43.340]請你少買冰淇淋\n[00:45.680]天涼就别穿短裙\n[00:47.830]别再那麼淘氣\n[00:50.060]如果有時不那麼開心\n[00:52.470]...
- 咱們會發現請求下來的歌詞是有規律的: \n 為換行
請求歌詞資料的時機
- 什麼時候請求歌詞資料:
- 在
或元件被渲染完成
切換歌曲時
- 請求
的歌詞或者目前播放音樂
點選頁面上的歌曲
- 在
封裝歌詞解析工具函數(邏輯思路)
1.使用slice切割字元串
2.建立正則解析規則: 将"[00:26.810]傻站在你家樓下..." 解析成-> 00:26.810
3.注意: 最後一行也有\n在周遊時加個判斷,如果為不為空執行下面操作
4.擷取正則解析的3個時間轉換為毫秒, 分鐘:秒數:00*10 000就是毫秒(加個判斷*1轉換為number類型)
5.将擷取的3個毫秒數相加: 目前歌曲播放的總時長(毫秒)
6.擷取目前播放的歌詞: replace方法 (完成效果如下👇)
[
0: {totalTime: 0, content: "作曲 : 許嵩"}
1: {totalTime: 1000, content: "作詞 : 許嵩"}
2: {totalTime: 22240, content: "天空好想下雨"}
3: {totalTime: 24380, content: "我好想住你隔壁"}
]
7.将資料儲存到redux當中
8.注意: 在切換歌曲時有可能會報 Cannot read property '1' of null , 這是因為從result讀取屬性時沒找到, 加一個if判斷,如果result沒有值的話, 執行關鍵字continue跳轉到判斷條件重新執行
歌詞解析代碼
const parseExp = /\[([0-9]{2}):([0-9]{2})\.([0-9]{2,3})\]/
export function parseLyric(lyrics) {
if(!lyrics) return
const lineStrings = lyrics.split('\n')
const lyricList = []
for (const line of lineStrings) {
if (line) {
const result = parseExp.exec(line)
if(!result) continue
const time1 = result[1] * 60 * 1000
const time2 = result[2] * 1000
const time3 = result[3].length > 2 ? result[3] * 1 : result[3] * 1000
// 目前歌曲播放的總時長(毫秒)
const totalTime = time1 + time2 + time3
const content = line.replace(parseExp, '').trim()
const lineObj = {totalTime, content};
lyricList.push(lineObj)
}
}
return lyricList
}
完成效果如下圖
拿到目前播放的歌詞
在歌曲播放的時候會有一個 currentTime 變量,
拿到這個變量和目前播放的歌詞中的 time 進行比對,
小于歌詞中time,之後擷取索引值-1(要展示的歌詞是前一句)
在timeUpdate事件中: 擷取目前播放的歌詞
1.擷取歌詞的索引
2.周遊歌詞數組.Length
3.判斷目前播放時間小于歌詞播放時間,擷取目前循環的索引
注意: 注意時間問題,轉換為毫秒(current)進行對比判斷
4.從歌詞數組取出索引拿到目前播放的歌詞
優化: 對for循環進行優化
5.對歌詞進行管理: 由于歌詞在多處使用,使用redux進行管理
優化: dispatch action 過于頻繁.
解決: index如果沒有變不需要dispatch(index和currentLyricIndex對比)
實作效果
展示歌詞
- 使用
元件Antd Message
- 修改内置樣式
- 實作效果
- 到現在我們已經完成「網易雲音樂PC」首頁基本功能,相信你對React全家桶已經是比較熟練了,接下來想往哪方面擴充可以自行補充完善功能(不過相信能看到這裡的小夥伴估計沒幾個)😂;
- 如果文章中有哪部分不明白的或寫的不好或是有什麼建議歡迎大家提出🤗,希望大家共同進步;
最後
- 非常感謝王紅元老師的
讓我學習到很多React核心技術實戰
的知識。React
- 非常感謝背景提供者
,接口很穩定,文檔很完善Binaryify