項目功能說明
- 可順序、單曲循環(huán)、隨機播放音樂
- 播放上一曲、下一曲音樂
- 歌詞的實時展示
- 歌曲滾動條實時
- 點擊歌曲添加到播放列表
項目技術
- react
- antd組件庫
- 用react-routerconfig進行路由管理
- Hooks的useState進行狀態(tài)管理;
- 后端使用網易云API接口獲取數據
- axios進行交互。
文件目錄結構說明
- assets: 存放項目資源,包括css,img,font等
- common:公共的數據、常量等
- components:多個頁面間共享的組件
- pages:劃分各個頁面
- router:路由配置
- services:網絡請求
- store:redux合并
- utils:js的工具
導入文件的規(guī)范
- 導入第三方模塊(最上)
- 功能性東西(網絡請求,actionCreators、utils)
- 導入組件
本項目使用React.memo()來幫助我們控制何時重新渲染組件。組件僅在它的 props 發(fā)生改變的時候進行重新渲染。通常來說,在組件樹中 React 組件,只要有變化就會走一遍渲染流程。但是通過 PureComponent 和 React.memo(),我們可以僅僅讓某些組件進行渲染,從而提升性能。PureComponent 用于類組件,React.memo()用于函數組件
css重置
安裝normalize.css yarn add normalize.css,并通過@import "~normalize.css";模塊化導入。
項目配置別名
yarn add @craco/craco-
在package.json內修改項目啟動配置
原package.json
修改后 - 在根目錄下創(chuàng)建craco.config.js添加配置
- 重新啟動該項目
API接口

網易云音樂接口.png
項目結構劃分
添加路由
yarn add react-router-dom- 使用
yarn add react-router-config將所有路由配置統(tǒng)一管理 - 使用
renderRoutes函數完成配置 - 網易云音樂使用HashRouter
AppHeader和AppFooter布局
- 安裝
yarn add style-components,編寫css樣式 - 導航欄采用flex布局
- 導航欄前三項為路由跳轉,后三項為超鏈接跳轉
- 導航欄選項卡提前配置成數組數據,在
local-config.js文件內導出,使用時只需要遍歷數組即可 - 搜索框使用antdesign
yarn add antd
引入yarn add @ant-design/icons,使用圖標庫
@import "~ant/dist/antd.css";在reset.css內引入
discover子路由的搭建和配備
- discover文件夾下創(chuàng)建c-pages文件夾,放入子組件
- 項目中引入axios發(fā)送網絡請求
yarn add axios
使用Hooks useState和axios獲取數據
使用antDesign組件制作輪播圖(走馬燈)
- 輪播圖背景圖:高斯模糊圖片;是由原圖片拼接上
"?imageView&blur=40x20"后得到。
輪播圖下內容制作
- 將每個相似樣式的標題組件抽出到公共組件中,
components文件夾下theme-header-rcm文件夾中 - 熱門推薦中每個歌曲封面相同樣式也抽到公共組件中,
components文件夾下songs-sover文件夾中 - 對音樂播放量數字處理的函數
getCount()寫在utils文件夾的format-utils.js文件內 - 優(yōu)化頁面性能:圖片大小優(yōu)化(對獲取到的封面圖片大小處理的函數
getSizeImage()也寫在utils文件夾的format-utils.js文件內)
新碟上架
- 新碟上架的分頁效果采用輪播圖方法制作
榜單
- 榜單的自定義組件也在components文件夾內的top-ranking文件內
播放音樂
- 使用h5新增audio標簽播放音樂,通過增加點擊事件播放音樂;使用antd的滑動輸入條組件更改樣式模仿
//播放音樂
useEffect(() => {
audioRef.current.src = getPlayUrl(song.id);
}, [currentSong]);
- 使用audio標簽中的onTimeUpdate={CurrentTime}屬性獲得當前音樂播放時間,利用useState()保存音樂進度時間
const [currentTime, setCurrentTime] = useState(0);
const timeUpdate = (e) => {
// console.log(e.target.currentTime);
// e.target.currentTime(獲得當前播放音樂的進度時間) *1000 轉換為毫秒
setCurrentTime(e.target.currentTime * 1000)
}
- 實時更新進度條:antd的滑動輸入條自帶value={progress}屬性,可以實時更新進度
// 當前進度條進度
const [progress, setProgress] = useState(0);
//寫在timeUpdate 回調函數內
setCurrentTime(e.target.currentTime * 1000)
//duration 為當前音樂的總時長(s)
新增
const [isChanging, setIsChanging] = useState(false);來判斷當前進度條是否正在改變,以便于當用戶正在播放音樂并滑動滾動條時,滾動條可以滑動-
利用antd的滑動輸入條自帶onChange和onAfterChange的API可以得到進度條滑動的位置和滑動后結束的位置
antd滑動輸入條API useCallback()使用:當把一個回調函數傳入到自定義組件內部的時候用其做一個包裹;目的是不希望子組件進行多次渲染;是為了進行性能的優(yōu)化
// 滑動中的位置
const sliderChange = useCallback((value) => {
setIsChanging(true);
// 滑動滾動條時,實時更新時間的改變
const currentTime = value /100 * duration ;
setCurrentTime(currentTime)
setProgress(value) ;
// console.log("change:",value);
},[duration]);
//滑動結束后的位置
const sliderAfterChange = useCallback( (value) => {
//互動進度條后的進度條時間
const currentTime = value / 100 * duration /1000;
audioRef.current.currentTime = currentTime;
// 重新更新進度條時間
setCurrentTime(currentTime * 1000);
setIsChanging(false);
//如果沒有播放音樂,當滑動滾動條后開始播放音樂
if(!isPlaying){
playMusic();
}
}, [duration, isPlaying,playMusic] )
- 播放和暫停播放
// 點擊事件
const playMusic = () => {
if(!isPlaying){ // 播放音樂
audioRef.current.play();
// console.log("播放");
}
else{ //暫停音樂
audioRef.current.pause();
// console.log("暫停");
}
setIsPlaying(!isPlaying);
}
音樂播放工具欄的實現
- 邏輯為:當點擊推薦音樂的榜單組件內每個歌曲的播放圖標時,判斷該歌曲是否在播放列表內,如果不在則添加該歌曲到播放列表內并播放該歌曲;如果在則獲取該歌曲在列表中的位置,將其位置放在最頂端,并播放該歌曲。
- 跨組件間的事件傳遞使用到了事件總線,安裝events
yarn add events
events常用的API:- 創(chuàng)建EventEmitter對象:eventBus對象;
- 發(fā)出事件:eventBus.emit("事件名稱", 參數列表
- 監(jiān)聽事件:eventBus.addListener("事件名稱", 監(jiān)聽函數);
- 移除事件:eventBus.removeListener("事件名稱", 監(jiān)聽函數);
歌詞
- 展示歌詞部分也使用的是antd的message組件
- 通過api接口獲取到歌詞并解析展示
項目中出現的問題
- 背景圖片引入出現問題
本來打算使用字符串拼接背景圖片地址,使用require()導入本地圖片
background-image: url(${require("@/assets/img/banner_sprite.png")});
但是這樣無法獲取到本地圖片地址,我猜測是項目配置別名@craco使得無法使用@獲取到圖片地址。
我的解決辦法:
import url2 from '@/assets/img/banner_sprite.png'
background-image: url(${url2});
- 加載圖片
使用useEffect()獲取信息,使用useState保存信息狀態(tài),并賦予他們初始狀態(tài)為[](空數組)。因為獲取到的信息仍然為一個數組,若要取得屬性,則要通過數組下標來獲取,但是打印空數組[0]為undefined,此時在獲取他的屬性就會報錯。
我的解決辦法:
const Song = currentSong[0] || "";
const picUrl = (Song.al && Song.al.picUrl) || "";
若獲取到的值為undefine,則賦予“”。
- 播放音樂滑動進度條后,進度條不會立即到滑動的位置,而是會先回彈一下。
原因:在獲取當前音樂播放時間時,利setCurrentTime(e.target.currentTime * 1000),但是e.target.currentTime無法更加實時的獲得當前滑動的數據,所以或出現上述bug。
我的解決辦法:在滑動結束后的回調函數沖重新更新進度條時間
//滑動結束后的位置
const sliderAfterChange = useCallback( (value) => {
//互動進度條后的進度條時間
const currentTime = value / 100 * duration /1000;
audioRef.current.currentTime = currentTime;
// 重新更新進度條時間
setCurrentTime(currentTime * 1000);
setIsChanging(false);
}, [duration] )
- 播放音樂滑動進度條后,時間剛開始會隨著進度條的滑動而改變,轉而瞬間按照正在播放的音樂的時間變化。
原因:當前時間沒有放進isChanging中,(啊哈哈哈哈哈笑死了)
解決辦法:
// 控制當前進度條是否正在改變
const [isChanging, setIsChanging] = useState(false);
//當前音樂播放時
const timeUpdate = (e) => {
// console.log(e.target.currentTime);
// e.target.currentTime(獲得當前播放音樂的進度時間) *1000 轉換為毫秒
if(!isChanging){
setProgress(currentTime / duration * 100);
setCurrentTime(e.target.currentTime * 1000);
}
}


