本篇開始作 「網易雲音樂PC」項目,建議最好有如下基礎
react、redux、redux-thunk、react-router
,上一章只是對項目進行初步介紹認識,本章節會帶你完成:網易雲的基本骨架結構並完成使用redux-immutable
重構redux
css<details>
<summary>本章節完成結果以下</summary>html<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f6680e5c03844424827672caa46dabba~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" /></details>前端
爲了更便捷的開發項目,推薦安裝如下vscode
插件react
ESLint
: 代碼風格檢查工具,幫助咱們規範代碼書寫vscode-styled-components
: 在編寫styled-components
中語法高亮顯示和樣式組件的ES7 React/Redux/GraphQL/React-Native snippets
: 代碼片斷chrome
插件webpack
redux
數據json
數據進行美化create-react-app
腳手架初始化項目結構: create-react-app music163_xxx
│─src ├─assets 存放公共資源css和圖片 ├─css 全局css ├─img ├─common 公共的一些常量 ├─components 公共組件 ├─pages 路由映射組件 ├─router 前端路由配置 ├─service 網絡配置和請求 └─store 全局的store配置 └─utils 工具函數 └─hooks 自定義hook
項目樣式重置選擇:ios
normalize.css
+ custom.css
(也就是自定義的css
)安裝normalize.css
: yarn add normalize.css
git
css
文件引入: src->assets->css-> normalize.css
↓首先下載項目資源(都是項目使用到的一些背景圖和精靈圖)github
github
文件慢,參考個人這篇文章加速🚀加載文件下面的全局CSS
是用於頁面初始化,若是你的css
掌握的不錯,那麼建議直接拷貝😏web
css
拷貝到全局自定義的css
文件當中(src -> assets -> css -> reset.css
)/* reset.css (自定義的css) */ @import '~normalize.css'; /* 後續有說明,先跳過便可(安裝完antd再導入的) */ /* @import '~antd/dist/antd.css'; */ /* 樣式的重置 */ body, html, h1, h2, h3, h4, h5, h6, ul, ol, li, dl, dt, dd, header, menu, section, p, input, td, th, ins { padding: 0; margin: 0; } ul, ol, li { list-style: none; } a { text-decoration: none; color: #666; } a:hover { color: #666; text-decoration: underline; } i, em { font-style: normal; } input, textarea, button, select, a { outline: none; border: none; } table { border-collapse: collapse; border-spacing: 0; } img { border: none; vertical-align: middle; } /* 全局樣式 */ body, textarea, select, input, button { font-size: 12px; color: #333; font-family: Arial, Helvetica, sans-serif; background-color: #f5f5f5; } .text-nowrap { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .w1100 { width: 1100px; margin: 0 auto; } .w980 { width: 980px; margin: 0 auto; } .text-indent { text-indent: -9999px; } .inline-block { display: inline-block; } .sprite_01 { background: url(../img/sprite_01.png) no-repeat 0 9999px; } .sprite_02 { background: url(../img/sprite_02.png) no-repeat 0 9999px; } .sprite_cover { background: url(../img/sprite_cover.png) no-repeat 0 9999px; } .sprite_icon { background: url(../img/sprite_icon.png) no-repeat 0 9999px; } .sprite_icon2 { background: url(../img/sprite_icon2.png) no-repeat 0 9999px; } .sprite_button { background: url(../img/sprite_button.png) no-repeat 0 9999px; } .sprite_button2 { background: url(../img/sprite_button2.png) no-repeat 0 9999px; } .sprite_table { background: url(../img/sprite_table.png) no-repeat 0 9999px; } .my_music { background: url(../img/mymusic.png) no-repeat 0 9999px; } .not-login { background: url(../img/notlogin.jpg) no-repeat 0 9999px; } .image_cover { position: absolute; left: 0; right: 0; top: 0; bottom: 0; text-indent: -9999px; background: url(../img/sprite_cover.png) no-repeat -145px -57px; } .sprite_player { background: url(../img/playbar_sprite.png) no-repeat 0 9999px; } .lyric-css .ant-message-notice-content { position: fixed; left: 50%; bottom: 50px; transform: translateX(-50%); background-color: rgba(0,0,0,.5); color: #f5f5f5; } .wrap-bg2 { background: url(../img/wrap3.png) repeat-y center 0;; }
第一步:安裝craco:chrome
yarn add @craco/craco
第二步:修改package.json
文件
react-scripts
來管理的;craco
來管理;"scripts": { -"start": "react-scripts start", -"build": "react-scripts build", -"test": "react-scripts test", \+ "start": "craco start", \+ "build": "craco build", \+ "test": "craco test", }
第三步:在根目錄下建立 craco.config.js
文件用於修改默認配置↓
module.exports = { // 配置文件 }
// 根路徑 -> craco.config.js const path = require('path') const resolve = dir => path.resolve(__dirname, dir) module.exports = { webpack: { alias: { // @映射src路徑 '@': resolve('src'), 'components': resolve('src/components') } } }
使用router
動態渲染path
對應的組件,具體配置以下↓
src/pages
文件夾有建立discover和mine和friend
組件router
:yarn add react-router-dom
集中式配置路由映射:yarn add react-router-config
// src/router->index.js (配置路由映射) import { Redirect } from "react-router-dom"; import Discover from "@/pages/discover"; import Mine from "@/pages/mine"; import Friend from "@/pages/friend"; const routes = [ { path: "/discover", component: Discover }, { path: "/mine", component: Mine }, { path: "/friend", component: Friend }, ]; export default routes;
在App.js
使用HashRouter
組件包裹使用router-config
配置的路由映射(使路由映射表的配置生效):
<details>
<summary>點擊查看 App.js 中的配置</summary>
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ddc98a03ea644a8ca1c06c3f3e2db678~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />
</details>
header
組件中,使用NavLink
測試路徑切換,渲染對應組件URL
發生變化,注意路徑的變化和組件的切換<br/>
<br/>
styled-components
庫yarn add styled-components
Flex
實現功能:點擊頭部列表項,添加背景實現高亮和下面的小三角 實現思路:(利用`NavLink`組件被點擊有`active`的`className`單獨給class設置樣式便可) 1.NavLink點擊活躍後實現上面的效果 2.給NavLink設置自定義className,在對應的css文件實現效果
Antd
組件也能夠自行編寫Ant design
: yarn add antd
Ant design icons
: yarn add @ant-design/icons
1.在reset.css文件引入: antd樣式 ↓ @import '~antd/dist/antd.css'; 2.在Header.js引入icons 3.使用antd組件: Input組件 4.修改placehold文本樣式
鍵盤圖標
我是後來加的,能夠先暫時跳過,有興趣的朋友能夠作一下<br/>
<br/>
API接口文檔(可選1): 本地安裝部署
discover
頁面// src/router/router.js -> 對根路徑進行重定向到: /discover 👇 const routes = [ // `/`根路徑重定向到: /discover路徑 --->{ path: '/', exact: true, render: () => <Redirect to="/discover" /> },<---- { path: '/discover', component: JMDiscover } // ... ]
<details>
<summary>建立Discover文件夾下的對應的子組件</summary>
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0374a1352cba41ceb4ea090be4310160~tplv-k3u1fbpfcp-zoom-1.image" />
</details>
const routes = [ { path: '/', exact: true, render: () => <Redirect to="/discover" /> }, { path: '/discover', component: JMDiscover, --->routes: [ { path: '/discover', render: () => <Redirect to="/discover" /> }, { path: '/discover/recommend', component: JMRecommend }, { path: '/discover/ranking', component: JMRanking }, { path: '/discover/album', component: JMAlbum }, { path: '/discover/djradio', component: JMDjradio }, { path: '/discover/artist', component: JMArtist }, { path: '/discover/songs', component: JMSongs } ],<---- }, { path: '/mine', component: JMMine }, { path: '/friend', component: JMFriend }, ]
discover
頁面下渲染嵌套子路由// src->pages->discover->index.js export default memo(function JMDiscover(props) { const { route } = props return ( <div> ... {renderRoutes(route.routes)} </div> ) })
axios
安裝axios: yarn add axios
src
文件夾下新建service
文件夾📂用於網絡請求axios
封裝好的文件, 拷貝到該文件夾下axios
簡易封裝以下👇<details>
<summary>axios簡易封裝,點擊查看</summary>
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4a971fd109094419ae0aa82b74b43573~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />
</details>
如今讓咱們開始請求輪播圖數據:
<br/>
安裝:
yarn add redux
yarn add react-redux
yarn add redux-thunk
yarn add redux react-redux redux-thunk
<details>
<summary>組織目錄織結構</summary>
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb44faf4501f4218b644920af81e347f~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />
</details>
import { combineReducers } from "redux"; // 引入recommend頁面的store(下面能夠暫時不寫,跳到下第3小結) import { reducer as recommendReducer } from '../pages/discover/child-pages/recommend/store' // 將多個reducer合併 const cRducer = combineReducers({ // 下面能夠暫時不寫(下面能夠暫時不寫,跳到下第3小結) recommend: recommendReducer }) export default cRducer
import { createStore, applyMiddleware, compose } from "redux"; // 引入thunk中間件(可讓派發的action能夠是一個函數) import thunk from 'redux-thunk' // 引入合併後的reducer import cRducer from "./reducer"; // redux-devtools -> 瀏覽器插件 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // 建立store並傳遞: 1.reducer(純函數) 2.StoreEnhancer const store = createStore(cRducer, composeEnhancers( applyMiddleware(thunk) )) export default store
// 在App.js組件中使用react-redux import { Provider } from 'react-redux' import store from './store' export default memo(function App() { return ( <Provider store={store}> // ... {renderRoutes(routes)} </Provider> ) })
建立combinReducers
reducer
合併建立store
redux-devtools
配置react-redux
redux
輪播圖數據API接口:
redux
當中// src->page->dicover->child-pages->recommend->store->actionCreator.js (派發action用的) import * as actionTypes from './actionTypes' import { getTopBanners } from '@/service/recommend.js' // 輪播圖Action export const changeTopBannerAction = res => ({ type: actionTypes.CHANGE_TOP_BANNER, topBanners: res, }) // 輪播圖網絡請求 export const getTopBannersAction = () => { return dispatch => { // 發送網絡請求 getTopBanners().then(res => { dispatch(changeTopBannerAction(res)) }) } } // service->recommend.js ------推薦頁的輪播圖API接口----------- import request from './request' export function getTopBanners() { return request({ url: "/banner" }) } // page->dicover->child-pages->recommend.js import React, { memo, useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { getTopBannersAction } from './store/actionCreator' function JMRecommend(props) { // redux Hook 組件和redux關聯: 獲取數據和進行操做 const { topBanners } = useSelector(state => ({ topBanners: state.recommend.topBanners, })) const dispatch = useDispatch() useEffect(() => { dispatch(getTopBannersAction()) }, [dispatch]) return ( <div> <h2>JMRecommend</h2> <h3>{topBanners.length}</h3> </div> ) } export default memo(JMRecommend)
---> import { shallowEqual, useDispatch, useSelector } from 'react-redux' <--- const { topBanners } = useSelector(state => ({ topBanners: state.recommend.topBanners, ---> }), shallowEqual) <---
<br/>
Immutable
可讓redux
中的維護的state
不在是淺層拷貝再賦值, 而是使用Immutable
數據結構保存數據state
不會修改原有數據結構, 而是返回修改後新的數據結構, 能夠利用以前的數據結構而不會形成內存的浪費immutableJS
安裝: yarn add immutable
<details>
<summary>對項目當前目錄reducer使用ImmutableJS</summary>
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5aa0b42d08684a60bd905ee468283cb2~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20200927215952708" />
</details>
// 1.在reducer.js文件使用Immutable設置: discover->child-cpn->recommend->store->reducer.js /* --> */ import { Map } from "immutable"; //<--- import * as actionTypes from './actionTypes' // 使用Immutable管理redux中的state (修改的`state`不會修改原有數據結構, 而是返回修改後新的數據結構) const defaultState = Map({ topBanners: [], }) export default function reducer(state = defaultState, action) { switch (action.type) { case actionTypes.CHANGE_TOP_BANNER: /* ---> */ return state.set('topBanners', action.topBanners) //<--- default: return state } } // 2.在recommend的index.js文件獲取的是Immutable對象, 須要進行設置 const { topBanners } = useSelector(state => ({ ---> topBanners: state.recommend.get('topBanners') <--- }))
爲何不對項目根目錄的reducer
使用immutableJS?
reducer
是將多個reducer
進行合併的reducer
, 並且使用對combineRducer
返回的對象使用immutable
進行管理是不能合併的使用redux-immutable
:
yarn add redux-immutable
// 根目錄下src->store->reducer import { combineReducers } from 'redux-immutable' import { reducer as recommendReducer } from '../pages/discover/child-pages/recommend/store' // 多個reducer合併 const cRducer = combineReducers({ recommend: recommendReducer }) export default cRducer
在recommend.js
文件中修改獲取state
方式
state
是immutable
對象, 因此須要修改原有獲取方式// 在recommend👉c-cpns👉top-banners👉index.js文件 (獲取的是Immutable對象, 須要進行設置) const { topBanners } = useSelector(state => ({ // 下面兩行獲取state方式相等 // topBanners: state.get('recommend').get('topBanners') --> topBanners: state.getIn(['recommend', 'topBanners']) <-- }))
antd
走馬燈組件↓使用useRef
獲取跑馬燈組件暴露的切換輪播圖的方法:prev() next()
url
添加了其餘的參數咱們在網易雲官網發現其實背景圖只是添加了:
query string
參數( ?imageView&blur=40x20 )
?imageView&blur=40x20
style.js
中獲取URL顯示便可 (能夠先給banner傳遞一個固定的高斯模糊背景圖)實現思路:
beforeChange
, 切換面板前的回調並使用use Callback
將事件函數包裹
state
發生了改變, 該事件函數沒有變化, 卻被從新定義了的問題(簡單總結)currentIndex
用於記錄, 幻燈片切換的索引在事件函數參數有from to
to
參數是Carousel走馬燈傳遞函數的參數)to
變量做爲下一個切換的索引cureent index
下標來取出: redux
中保存的top Banners
數組對應的背景圖片const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")