Appearance
仿网易云音乐-react
2022.1.5
- 创建项目
- 规划目录
jsx
web_music
├─ public
│ └─ index.html
├─ README.md
└─ src
├─ App.jsx // 入口组件
├─ assets // 辅助文件
├─ common // 公共
├─ components // 公共组件
├─ index.js // 入口js
├─ pages // 页面组件
├─ redux // 状态管理器
├─ router // 路由
├─ services // 网络请求
└─ utils // 工具
css 重置 normalize
配置别名 @craco/craco 根目录新建 craco.config.js
jsx
const path = require('path')
const resolve = (dir) => path.resolve(__dirname, dir)
module.exports = {
// 使用 @craco/craco 配置别名
webpack: {
alias: {
'@': resolve('src'),
components: resolve('src/components')
}
}
}
安装路由并配置路由 react-router-dom@5.3.0 react-router-config 使用 renderRoutes(routes) 进行路由的自动渲染
封装 axios 请求数据
jsx
// config.js
// 在这个文件定义axios 的配置,不仅是react可以用,vue或者别的项目也应该这样做
const devBaseUrl = '开发地址'
const proBaseUrl = '生产地址'
export const BASE_URL =
process.env.NODE_ENV === 'development' ? devBaseUrl : proBaseUrl
export const TIMEOUT = 5000
// ---------------------------------------------------
// request.js
import axios from 'axios'
// 导入axios的配置
import { BASE_URL, TIMEOUT } from './config'
const instance = axios.create({
baseURL: BASE_URL,
timeout: TIMEOUT
})
// 还可以手动添加拦截器
// 请求拦截
instance.interceptors.request.use((config) => {
return config
})
// 响应拦截
instance.interceptors.response.use((res) => {
return res.data
})
export default instance
- 函数式组件中函数的书写顺序
- 组件中使用的 state
- 组件关联 redux:获取和操作数据
- 其他 hooks
- 其他业务逻辑
2022.01.08
创建 header 和 footer 组件
配置路由
jsx
import Discover from '../pages/Discover'
import Mine from '../pages/Mine'
import Friends from '../pages/Friends'
const routes = [
{
path: '/',
exact: true,
component: Discover
},
{
path: '/mine',
component: Mine
},
{
path: '/friends',
component: Friends
}
]
export default routes
2022.01.09
使用 styled-components 构建样式 在 styled-components 中使用 background-img 引入的图片,需要按照模块的方式先进行引入,使用${xxx} 获取引入的图片
调整 App-header 组件的样式
2022.01.12
使用 antd 构建 App-header 组件右侧的样式 yarn add antd
构建 App-footer 组件的样式
2022.01.13
react 路由的重定向 在定义路由 js 中,除了使用 component 以外,还可以使用 reder 函数,使其返回一个组件
jsx// 路由的重定向 import { Redirect } from 'react-router-dom' const routes = [ { path: '/', exact: true, render: () => <Redirect to="/discover" /> } ]
复制之前封装的 axios js,并测试
使用 redux 及 react-redux 进行状态管理
yarn add redux react-redux redux-thunk
[redux](E:\git\studyProgram\70 react 学习\11_再学 redux\README.md)
2022.01.14
- 使用 Immutable.js 操作 redux 中的数据(reducer),优化性能 yarn add immutable 主要是修改 reducer 中的默认数据,后续修改数据时,使用 immutable 提供的 API 即可。 Immutable 能优化性能的原因,最大化复用原本的数据。一定程度上解决 reducer 中默认数据很多的情况,只修改一个地方,就会将所有数据进行重新修改,重新渲染所有使用了数据的页面。
修改设置数据的方法
jsxconst defaultState = Map({ topBanners: [] })
修改 reducer 中设置数据的方法
jsxreturn preState.set('topBanners', data)
修改组件中使用了状态时,获取数据的方法
jsx// 获取 store 中存储的数据, const { topBanners } = useSelector( (state) => ({ topBanners: state.discover.get('topBanners') }), shallowEqual )
而合并全部 reducer,则不能直接使用 immutable ,需要使用 redux-immutable ,将原本的 combineReducers 的引入替换,redux-immutable 内部会自动帮助我们进行性能的优化。
jsx- import { combineReducers } from 'redux' + import { combineReducers } from 'redux-immutable' // 容器组件 const { topBanners } = useSelector( (state) => ({ - topBanners: state.discover.get('topBanners') * topBanners: state.get('discover').get('topBanners') // 还可以简写,意思是先获取 discover ,再获取 topBanners topBanners: state.getIn(['discover', 'topBanners']) }), shallowEqual )
2022-01-15
制作 recommend 页面中的轮播图 在调用 beforeChange(用于设置轮播图之后的背景图片的切换) 时,直接使用一个函数名就可以
还需要使用 useCallback 包裹 handleBannerChange 函数,进行性能优化
- useCallback 的使用时机:将函数传到自定义组件中.
jsx// 还需要使用 useCallback 来进行性能优化 const handleBannerChange = useCallback((from, to) => { setcurrentIndex(to) }, [])
制作 recommend 的中的通用标题组件 要传递数据,所以需要对数据进行校验和设置默认值,可以使用 propTypes
jsx// 对props数据类型进行限制 CommonListHeader.propTypes = { title: PropTypes.string.isRequired, keywords: PropTypes.array } // 对props数据设置默认值 CommonListHeader.defaultProps = { keywords: [] }
2022-01-16
制作通用的歌曲封面组件 SongCover
- 封装格式化播放量和设置请求图片大小的方法
制作底部播放相关组件 PlayBar
2022-01-17
修改底部播放相关组件 PlayBar 样式
修改精灵图的引入,改用重置样式的地方进行引入,避免轮播图切换时,图片闪动
增加 播放歌曲相关的 redux(action,reducer) 并在 PlayBar 组件中显示请求的对应数据
2022-01-18
完善拖动 PlayBar 进度条相关操作,修改播放和暂停按钮的切换
进度条修改时,不用 useState 也是可行的
jsx
const progressPercent = ((currentTime * 1000) / duration) * 100 || 0 // 设置播放进度条位置
// -----------------------------------------
// 后续替换为了,修改 当前播放时间的时候 也同时修改进度条位置
const timeUpdate = (e) => {
// 判断是否在拖动进度条中,如果拖动中不改变当前播放时间
if (!isChange) {
setcurrentTime(e.target.currentTime)
setprogressPercent(((currentTime * 1000) / duration) * 100)
}
}
// 并且还需要在拖动进度条的时候触发对应的修改事件
const sliderChange = useCallback(
(value) => {
// 改变是否在拖动的状态
setisChange(true)
// 修改进度条位置
setprogressPercent(value)
// 设置当前进度对应的时间
setcurrentTime(((value / 100) * duration) / 1000)
},
[isChange, duration]
)
将 PlayBar 组件拆分 在 reducer 中新增了多个与播放相关的初始化数据
增加音乐列表 musicList 在 reducer 中新增 musicList,currentSongIndex 等默认数据
2022-01-19
完善新增音乐到列表
增加播放模式的切换
下一首歌曲的播放
- 单曲循环 只需要将 audio.currentTime 设置为 0,并重新调用一下 audio.play() 即可,但是将歌曲的显示时间和进度条位置都保存在 redux 中,所以还需要使用 dispatch 重置一下 进度条和显示的时间
- 列表循环和随机播放 直接使用 下一曲按钮调用的 action 就可以了
- 还要考虑当前是否在播放状态中,如果是在播放状态中,继续播放,否则就不做操作
jsx// play.jsx useEffect(() => { // 设置请求歌曲的数据到 audio 中 audio.src = getPlaySong(currentSongs.id) if (isPlaying) { // 在useEffect中判断是否处于播放状态,如果处于播放状态,redux 中 currentSongs 发生了变化,也需要继续播放音乐,用于处理下一曲 audio.play() } }, [currentSongs, audio])
2022-01-20
- 理解 useEffect 中 ,依赖项会引起的刷新问题
jsx
useEffect(() => {
// 设置请求歌曲的数据到 audio 中
audio.src = getPlaySong(currentSongs.id)
if (isPlaying) {
// 在useEffect中判断是否处于播放状态,如果处于播放状态,redux 中 currentSongs 发生了变化,也需要继续播放音乐,用于处理下一曲
audio.play()
}
}, [currentSongs]) // 这里的依赖项不能增加 isPlaying 不然切换播放暂停会导致重新执行这个函数,使得播放的音乐重新播放
- 增加当前歌词的匹配和歌词滚动的效果
2022-01-21 ---> 2022-01-24
重构播放组件
重构播放组件逻辑代码,解决其中存在的问题
增加歌曲列表,增加在歌曲列表中删除歌曲的逻辑
jsx/* 判断是否是最后一首歌曲 - 不是 id 与 当前 播放的是否相同 只有相同时需要播放新的歌曲 - 相同 - 判断索引是否是音乐列表中的最后一首 是 => 索引为新列表最后一位 播放新的索引对应的歌 不是 => 索引不变 播放新列表中 旧索引的歌曲 - 不同 - 查找要删除的歌曲索引与当前索引对比 小于 => 当前索引减少一位 大于 => 当前索引不变 更新列表 */
2022-01-25
增加播放组件(PlayBar)的显示和隐藏逻辑,使用工具函数,解决 mouseover 和 mouseout 多次进入组件重复触发的问题。 核心是用 event 中的 relatedTarget 进行判断,使用 fromElement 判断进入和离开的的目标是否相同,如果为 null,则说明是离开组件,否则是在组件内部的元素
解决 点击其他位置,收起音乐列表的功能 一个 DOM 节点, 可以通过其 contains 方法来判断它是否包含一个元素, 也就是判断这个元素是否位于它自己内部, 如果这个元素是它自己, 同样是返回 true
jsx// Play.jsx document.addEventListener('click', (event) => { let ele = playBarRef.current let inele = ele.contains(event.target) if (!inele) { setshowMusicList(false) // 还要添加定时器 if (!alwaysShowPlayBar) { time = setTimeout(() => { setplayBarStatus(false) }, 4000) } } })
2022-01-26
- 使用正则匹配 匹配传递的 location 中的参数
jsx
const text = '?k=成都123&type=1'
const reg = new RegExp('(?<=/?k=).*?(?=&)') // 匹配两个字符之间的内容
const result = text.match(reg)[0] // 成都123
- 了解 auido.play() 是一个 Promise,可以使用 then 和 catch 。
- 可以在 catch 中捕获错误,当歌曲不能播放时,使用 antd 的全局提示,然后跳转下一首歌曲
2022-01-27
学习打包
路由的懒加载
- 修改路由配置 lazy 需要引入 const Discover = lazy(()=>{import('../pages/Discover')})
- 需要为引用了懒加载的组件提供一个 Suspense 进行包裹,避免因为懒加载时,页面没有加载出来提示的信息,直接为 renderRoutes 提供了 Suspense 进行包裹就可以了。
jsx
<Suspense fallback={<div></div>}>{renderRoutes(routes)}</Suspense>
测试发布部署 若需要访问在根目录下的子目录,只需修改 package.json 文件 添加
json"homepage":".",
即可
2022-01-27~2022-02-04
重写进度条组件 主要使用的是监听鼠标按下后的事件,再榜单鼠标移动的事件,最后在鼠标按下结束后取消事件绑定 如下
jsx//滑块添加拖拽事件 eBarDrag.current.addEventListener('mousedown', function (event) { //初始化鼠标开始拖拽的点击位置 const nInitX = event.clientX //初始化滑块位置 const nInitLeft = this.offsetLeft let nX = 0 //页面绑定鼠标移动事件 document.onmousemove = (event) => { //鼠标移动时取消默认行为,避免选中其他元素或文字 event.preventDefault() //获取鼠标移动后滑块应该移动到的位置 nX = event.clientX - nInitX + nInitLeft //限制滑块最大移动位置 if (nX >= nMax) { nX = nMax } //限制滑块最小移动位置 if (nX <= 0) { nX = 0 } // 父组件传递的事件 props.onChange(nX) } //鼠标松开绑定事件,取消页面上所有事件 document.onmouseup = function (event) { document.onmousemove = null document.onmouseup = null // 父组件传递的事件 props.onAfterChange(nX) } })
优化功能,基本完整播放组件的开发
使用进度组件的封装方式,手写一个音量控制组件
增加缓存的使用,刷新后记录播放的歌曲、歌曲列表、歌词列表、音量大小
重写播放相关的 action,只有在播放,且不是暂停切换播放的时候请求歌曲地址,节省网络请求。
2022-02~05-2022-02-10
- 优化播放进度条,修改 useEffect 事件,增加使用完成后,删除对应的事件。
- 切换下一曲的时候,不仅修改 redux 中的 currentPlayurl,还将 audio.src 也设置为空,用于解决切换歌曲后,音乐最大长度还是上一曲的,需要将歌曲的 src 设置为空'',才能在播放音乐-暂停后,切换到下一曲,拖动进度条时能正常拖动。
- 开发首页榜单相关内容,将操作相关的组件 播放、删除、收藏等按钮,封装成组件。
- 增加显示播放列表时,滚动条调整到播放歌曲的位置,并优化对应的逻辑。超过范围才动态改变滚动条的位置。
- 修改播放下一曲,播放模式只会是 0 的问题。
2022-02-11-2022-02-21
- 搜索页面各个组件 单曲、歌手、专辑、视频、歌词、歌单、声音主播、用户
- 搜索建议
- 搜索结果的正则匹配,并且将对应的文字改变样式
- 优化播放组件逻辑
- 搜索建议组件开发,搜索框失去焦点 blur 和 搜索建议框的点击事件冲突问题,搜索建议框不能使用 onclick 执行优先度比 blur 低,所以需要使用 onmouseDown