前言
本篇开始做「网易云音乐 PC」我的项目,倡议最好有以下根底
react、redux、redux-thunk、react-router
,上一章只是对我的项目进行初步介绍意识,本章节会带你实现:网易云的根本骨架构造并实现应用redux-immutable
重构redux
<details>
<summary>本章节实现后果如下</summary><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f6680e5c03844424827672caa46dabba~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />
</details>
我的项目初始化
前言 -vscode&chrome 插件(可选)
- 如果曾经装置过了能够抉择跳过,以下都是可选的,当然不装置也没问题
-
为了更便捷的开发我的项目,举荐装置以下
vscode
插件ESLint
: 代码格调查看工具,帮忙咱们标准代码书写vscode-styled-components
: 在编写styled-components
中语法高亮显示和款式组件的- path-alias: 别名门路有对应的智能提醒
ES7 React/Redux/GraphQL/React-Native snippets
: 代码片段
-
chrome
插件- Redux DevTools: 不便调试
redux
数据 - FeHelper: 对服务器返回的
json
数据进行丑化
- Redux DevTools: 不便调试
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. 我的项目款式抉择
-
我的项目款式重置抉择:
- [] reset.css
- [x]
normalize.css
+custom.css
(也就是自定义的css
)
-
装置
normalize.css
:yarn add normalize.css
- 在全局
css
文件引入:src->assets->css-> normalize.css
↓ -
首先下载我的项目资源(都是我的项目应用到的一些背景图和精灵图)
- 如果下载
github
文件慢,参考我的这篇文章减速???? 加载文件
- 如果下载
-
上面的全局
CSS
是用于页面初始化,如果你的css
把握的不错,那么倡议间接拷贝????- 将上面????
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:
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
组件
- 前提:在
- 装置
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/>
Header 头部组件
1. 头部组件款式编写
- 为了 避免多个组件中款式抵触 , 组件内应用款式
styled-components
库 - 装置应用:
yarn add styled-components
- 布局应用:
Flex
2. 头部区域划分
3. 头部区域实现及思路(左)
实现性能:点击头部列表项,增加背景实现高亮和上面的小三角
实现思路:(利用 `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. 我的项目接口文档
-
API 接口文档(可选 1): 本地装置部署
- API 接口文档装置
- 应用: 本地
Node
服务器运行 - API 服务器运行
- 我的项目应用到的接口(可选 2): http://123.57.176.198:3000
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>
- 在
-
当初让咱们开始申请轮播图数据:
- 轮播图 API: /banner
- 演示: http://123.57.176.198:3000/banner
<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>
)
})
小结
-
创立
combinReducers
- 用于将多个
reducer
合并
- 用于将多个
-
创立
store
- 配置中间件和
redux-devtools
- 配置中间件和
-
配置
react-redux
- 帮忙咱们实现连贯
redux
- 帮忙咱们实现连贯
3. 轮播图数据通过 redux-thunk 来申请
-
轮播图数据 API 接口:
- /banner
- 演示????: http://123.57.176.198:3000/banner
- 将申请下来的的轮播图数据,放到
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 性能优化
- 具体细节参考: 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 治理数据
- 应用 redux-immutable 中的 combineReducers;
- 所有的 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
形式- 因为原有
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']) <--
}))
举荐页 Banner
1. 轮播图区域布局
- 轮播图组件布局: TopBanner
- 轮播图采纳
antd
走马灯组件↓
2. 应用 antd 走马灯组件
- 应用走马灯组件
-
应用
useRef
获取跑马灯组件裸露的切换轮播图的办法:prev() next()
- 应用两个按钮管制切换下一张图片 https://ant-design.gitee.io/c…
3. 背景高斯含糊实现
- 在咱们在网易云音乐官网,切换轮播图的时候,会发现轮播图在切换的时候会有突变成果
- 如何实现突变成果:其实就是一张背景图片, 只不是在申请背景图
url
增加了其余的参数
增加高斯含糊背景
-
咱们在网易云官网发现其实背景图只是增加了:
- 查问字符串也就是
query string
参数(?imageView&blur=40×20)
- 查问字符串也就是
?imageView&blur=40x20
- 只须要给 Banner 元素传递背景图片 URL,通过属性穿透在
style.js
中获取 URL 显示即可 (能够先给 banner 传递一个固定的高斯含糊背景图)
-
实现思路:
- 监听轮播图的切换 走马灯组件有对应的 API
beforeChange
, 切换面板前的回调 -
并应用
use Callback
将事件函数包裹- <details>
<summary> 如果不理解该 Hook 点我 </summary>
解决了: 当父组件的其余state
产生了扭转, 该事件函数没有变动, 却被从新定义了的问题(简略总结)
</details> - 定义组件的
currentIndex
用于记录, 幻灯片切换的索引
- <details>
-
在事件函数参数有
from to
- (
to
参数是 Carousel 走马灯传递函数的参数) - 咱们这里应用
to
变量作为下一个切换的索引
- (
- 依据
cureent index
下标来取出:redux
中保留的top Banners
数组对应的背景图片
const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")
- 监听轮播图的切换 走马灯组件有对应的 API
实现成果