基於React全家桶開發「網易雲音樂PC」項目實戰(二)

前言

本篇開始作 「網易雲音樂PC」項目,建議最好有如下基礎react、redux、redux-thunk、react-router上一章只是對項目進行初步介紹認識,本章節會帶你完成:網易雲的基本骨架結構並完成使用redux-immutable重構reduxcss

<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&chrome插件(可選)

  • 若是已經安裝過了能夠選擇跳過,如下都是可選的,固然不安裝也沒問題
  • 爲了更便捷的開發項目,推薦安裝如下vscode插件react

    • ESLint: 代碼風格檢查工具,幫助咱們規範代碼書寫
    • vscode-styled-components: 在編寫styled-components中語法高亮顯示和樣式組件的
    • path-alias: 別名路徑有對應的智能提示
    • ES7 React/Redux/GraphQL/React-Native snippets: 代碼片斷
  • chrome插件webpack

1.項目目錄劃分

  • 使用create-react-app腳手架初始化項目結構: create-react-app music163_xxx
  • 目錄結構也能夠按照本身習慣的結構來劃分
│─src
  ├─assets 存放公共資源css和圖片
    ├─css  全局css
    ├─img  
  ├─common  公共的一些常量
  ├─components 公共組件
  ├─pages   路由映射組件
  ├─router  前端路由配置
  ├─service 網絡配置和請求
  └─store   全局的store配置
  └─utils   工具函數
  └─hooks   自定義hook

2.項目樣式選擇

  • 項目樣式重置選擇:ios

    • [ ] reset.css
    • [x] normalize.css + custom.css(也就是自定義的css)
  • 安裝normalize.css: yarn add normalize.cssgit

    • 在全局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;;
}

3.配置路徑別名

  • 第一步:安裝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')
    }
  }
}

項目結構劃分

header組件

  • 狀態:固定,不會隨着URL發生變化
  • 組件存放:src/"components/app-header"文件夾中
  • 先點擊查看要完成效果

footer組件

  • 狀態:固定,不會隨着URL發生變化
  • 組件存放:src/"components/app-footer"文件夾中
  • 先點擊查看要完成效果

main主體內容

  • 主體內容會是隨着路徑變化動態的發生改變的
  • 使用router動態渲染path對應的組件,具體配置以下↓

    • 前提: 在src/pages文件夾有建立discover和mine和friend組件
  1. 安裝routeryarn add react-router-dom
  2. 集中式配置路由映射: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;
  3. 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/>

Header頭部組件

1.頭部組件樣式編寫

  • 爲了防止多個組件中樣式衝突, 組件內使用樣式styled-components
  • 安裝使用: yarn add styled-components
  • 佈局使用: Flex

2.頭部區域劃分

3.頭部區域實現及思路(左)

header-item-active

實現功能:點擊頭部列表項,添加背景實現高亮和下面的小三角
實現思路:(利用`NavLink`組件被點擊有`active`的`className`單獨給class設置樣式便可)
   1.NavLink點擊活躍後實現上面的效果
   2.給NavLink設置自定義className,在對應的css文件實現效果

4.頭部區域實現及思路(右)

  • 右側能夠使用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/>

Footer底部組件

1.底部區域佈局

2.實現效果

<br/>

路由優化和API說明

1.項目接口文檔

2.路由優化_重定向

  • 對'根路由'進行重定向到: discover頁面
//  src/router/router.js -> 對根路徑進行重定向到: /discover   👇
const routes = [
    //   `/`根路徑重定向到: /discover路徑
--->{ path: '/', exact: true, render: () => <Redirect to="/discover" /> },<----
    { path: '/discover', component: JMDiscover }
  //  ...
]

3.嵌套路由

佈局劃分

建立對應的子組件

<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 },
]

渲染嵌套子路由config

  • discover頁面下渲染嵌套子路由
// src->pages->discover->index.js
export default memo(function JMDiscover(props) {
  const { route } = props
    return (
    <div>
       ...
      {renderRoutes(route.routes)}
    </div>
  )
})

完成效果↓

4.輪播圖API

  • 開始發送網絡請求使用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/>

redux保存服務器返回的數據

1.安裝redux

  • 安裝:

    • 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>

2.配置redux

項目根目錄src下的store → reducer.js

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

項目根src下store → index.js

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

項目根src目錄下app.js文件中 → 配置react-redux

// 在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>
  )
})

小結

  1. 建立combinReducers

    • 用於將多個reducer合併
  2. 建立store

    • 配置中間件和redux-devtools
  3. 配置react-redux

    • 幫助咱們完成鏈接redux

3.輪播圖數據經過redux-thunk來請求

  • 輪播圖數據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)

4.useSelector性能優化

---> import { shallowEqual, useDispatch, useSelector } from 'react-redux'  <---

const { topBanners } = useSelector(state => ({
  topBanners: state.recommend.topBanners,
---> }), shallowEqual)  <---

<br/>

結合ImmutableJS

immutableJS介紹及安裝

  • immutableJS介紹: 使用Immutable可讓redux中的維護的state不在是淺層拷貝再賦值, 而是使用Immutable數據結構保存數據
  • 使用immutableJS好處: 修改的state不會修改原有數據結構, 而是返回修改後新的數據結構, 能夠利用以前的數據結構而不會形成內存的浪費
  • immutableJS安裝: yarn add immutable

結合Redux管理數據

  1. 使用redux-immutable中的combineReducers;
  2. 全部的reducer中的數據都轉換成Immutable類型的數據

immutableJS融入項目

<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')  <---
  }))

redux-Immutable

  • 爲何不對項目根目錄的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方式

    • 由於原有stateimmutable對象, 因此須要修改原有獲取方式
// 在recommend👉c-cpns👉top-banners👉index.js文件 (獲取的是Immutable對象, 須要進行設置)
const { topBanners } = useSelector(state => ({
    // 下面兩行獲取state方式相等
    // topBanners: state.get('recommend').get('topBanners')
--> topBanners: state.getIn(['recommend', 'topBanners']) <--
  }))

推薦頁Banner

1.輪播圖區域佈局


  • 輪播圖組件佈局: TopBanner

  • 輪播圖採用antd走馬燈組件↓

2.使用antd走馬燈組件

3.背景高斯模糊實現

  • 在咱們在網易雲音樂官網,切換輪播圖的時候,會發現輪播圖在切換的時候會有漸變效果
  • 如何實現漸變效果:其實就是一張背景圖片, 只不是在請求背景圖url添加了其餘的參數

添加高斯模糊背景

  • 咱們在網易雲官網發現其實背景圖只是添加了:

    • 查詢字符串也就是query string參數( ?imageView&blur=40x20 )

網易雲音樂 高斯模糊背景圖 ?imageView&blur=40x20

    • 只須要給Banner元素傳遞背景圖片URL,經過屬性穿透在style.js中獲取URL顯示便可 (能夠先給banner傳遞一個固定的高斯模糊背景圖)
    • 實現思路:

      1. 監聽輪播圖的切換 走馬燈組件有對應的APIbeforeChange, 切換面板前的回調
      2. 並使用use Callback將事件函數包裹

        • <details>
          <summary>若是不瞭解該Hook點我</summary>
          解決了: 當父組件的其餘state發生了改變, 該事件函數沒有變化, 卻被從新定義了的問題(簡單總結)
          </details>
        • 定義組件的currentIndex用於記錄, 幻燈片切換的索引
      3. 在事件函數參數有from to

        • (to參數是Carousel走馬燈傳遞函數的參數)
        • 咱們這裏使用to變量做爲下一個切換的索引
      4. 根據cureent index下標來取出: redux中保存的top Banners數組對應的背景圖片
      const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")

    實現效果

    banner-switch

    相關文章
    相關標籤/搜索