S13-08 React-项目:mr-react18-ts-music
[TOC]
接口
baseURL:https://coderwhy-music.vercel.app
【2023-07-28 当前可用】baseURL:http://codercba.com:9002/
baseURL:http://codercba.com:9001/
项目搭建
创建项目
1、使用create-react-app
创建项目的缺点
2、推荐如下创建
创建 TS 项目时添加参数:--template typesc
create-react-app mr-react18-ts-music --template typescript
初始化
1、删除多余的文件
2、index.tsx
3、App.tsx
import React from "react"; function App() { return
<div className="App">App</div>
; } export default App;
项目配置
项目 icon
替换原本的 favicon.ico
项目标题
项目别名等
1、安装插件:craco
npm i @craco/craco@alpha -D
2、修改package.json
中的 scripts
3、创建craco.config.js
,配置 webpack 别名
const path = require("path");
+ const resolve = (dir) => path.resolve(__dirname, dir);
module.exports = {
webpack: {
alias: {
+ "@": resolve("src"),
},
},
};
4、在tsconfig.json
中配置
目录结构
重置 CSS 样式
normalize.css
- 依赖包:normalize.css
1、安装:normalize.css
npm i normalize.css
2、在main.ts
中导入
import 'normalize.css'
reset.less
自定义重置:reset.less
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
dl,
dd,
ul,
ol,
li,
form,
input,
textarea,
th,
td,
select,
div,
section,
nav,
span,
i {
margin: 0;
padding: 0;
box-sizing: border-box;
}
em {
font-style: normal;
}
ul,
ol,
li {
list-style: none;
}
a {
text-decoration: none;
color: #333;
}
img {
border: none;
vertical-align: top;
}
/* img { font-size: 0; } */
input,
textarea {
outline: none;
}
textarea {
resize: none;
overflow: auto;
}
body {
font-size: 14px;
}
common.less
公共样式:common.less
body {
font-size: 14px;
font-family: '';
}
问题:
1、Vue: Vite 默认不能识别less
文件,需要安装less
npm i less -D
2、 React: webpack 默认不能识别less
文件,需要安装craco-less
- 安装
craco-less
# 由于版本不匹配,必须安装 craco-less@2.1.0-alpha.0 这个版本
npm i craco-less@2.1.0-alpha.0 -D
- 配置
craco.config.js
代码规范
集成 editorconfig 配置
.editorconfig
有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。
# http://editorconfig.org
root = true # 当前的配置在根目录中
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行尾的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
VSCode 需要安装一个插件:EditorConfig for VS Code
使用 prettier 工具
Prettier
是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。
1、安装 prettier
npm install prettier -D
2、配置.prettierrc
或者.prettierrc.json
文件:
- useTabs:使用 tab 缩进还是空格缩进,选择 false;
- tabWidth:tab 是空格的情况下,是几个空格,选择 2 个;
- printWidth:当行字符的长度,推荐 80,也有人喜欢 100 或者 120;
- singleQuote:使用单引号还是双引号,选择 true,使用单引号;
- trailingComma:在多行输入的尾逗号是否添加,设置为
none
表示不加; - semi:语句末尾是否要加分号,默认值 true,选择 false 表示不加;
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "none",
"semi": false
}
3、创建.prettierignore
忽略文件
/build/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
4、VSCode 需要安装 prettier 的插件:Prettier - Code formatter
5、测试 prettier 是否生效
测试一:在代码中保存代码;
可以通过插件
Prettier - Code formatter
实现测试二:配置一次性格式化所有文件的命令;
在 package.json 中配置一个 scripts:
sh"prettier": "prettier --write ."
让 prettier 在保存时自动格式化
- 1、在 vscode 中安装 Prettier 扩展
- 2、在
设置
中搜索format on save
,选中Editor: Format On Save
- 3、在
设置
中搜索default format
,设置Editor: Default Formatter
为Prettier - Code formatter
- 4、配置
.prettierrc
- 5、实现保存代码时自动格式化
使用 ESLint 检测
配置 Vue 中的 ESLint
1、在前面创建项目的时候,我们就选择了 ESLint,所以 Vue 会默认帮助我们配置需要的 ESLint 环境。
2、VSCode 需要安装 ESLint 插件:ESLint
3、解决 eslint 和 prettier 冲突的问题:
安装插件:(vue 在创建项目时,如果选择 prettier,那么这两个插件会自动安装)
eslint-plugin-prettier
(主要)eslint-config-prettier
npm i eslint-plugin-prettier eslint-config-prettier -D
添加 prettier 插件:
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint",
+ // "@vue/eslint-config-prettier/skip-formatting" // 该规范导致eslint没有提示
+ '@vue/eslint-config-prettier',
+ "plugin:prettier/recommended"
],
4、手动修改 eslint 检测规则
在出现提示的位置,复制出现的错误:
@typescript-eslint/no-unused-vars
在
.eslintrc.cjs
中添加如下代码:jsmodule.exports = { + rules: { + '@typescript-eslint/no-unused-vars': 'off' + } }
配置 React 中的 ESLint
1、手动安装 ESLint
npm i eslint -D
2、配置
npx eslint --init
配置时的选项
3、修改运行环境,添加 node 环境
4、VScode 中 eslint 的配置【非必要】
位置:首选项 - 设置 - settings.json
"eslint.lintTask.enable": true,
"eslint.alwaysShowStatus": true, // 始终显示eslint状态【可以添加】
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
5、解决 eslint 和 prettier 冲突的问题
5.1、安装插件:
eslint-plugin-prettier
(主要)eslint-config-prettier
npm i eslint-plugin-prettier eslint-config-prettier -D
5.2、添加 prettier 插件:
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
+ "plugin:prettier/recommended"
],
4、手动修改 eslint 检测规则
在出现提示的位置,复制出现的错误:
@typescript-eslint/no-unused-vars
在
.eslintrc.cjs
中添加如下代码:jsmodule.exports = { + rules: { + '@typescript-eslint/no-unused-vars': 'off' + } }
git-Husky 和 eslint
虽然我们已经要求项目使用 eslint 了,但是不能保证组员提交代码之前都将 eslint 中的问题解决掉了:
也就是我们希望保证代码仓库中的代码都是符合 eslint 规范的;
那么我们需要在组员执行
git commit
命令的时候对其进行校验,如果不符合 eslint 规范,那么自动通过规范进行修复;
那么如何做到这一点呢?可以通过 Husky 工具:
husky
是一个 git hook 工具,可以帮助我们触发 git 提交的各个阶段:pre-commit
、commit-msg
、pre-push
如何使用 husky 呢?
这里我们可以使用自动配置命令:
npx husky-init && npm install
注意: 在 windows 的 powershell 中需要给&&添加引号
npx husky-init '&&' npm install
这里会做三件事:
1.安装 husky 相关的依赖:
2.在项目目录下创建 .husky
文件夹:
npx huksy install
3.在 package.json 中添加一个脚本:
接下来,我们需要去完成一个操作:在进行 commit 时,执行 lint 脚本:
这个时候我们执行 git commit 的时候会自动对代码进行 lint 校验。
git-commit 规范
代码提交风格
通常我们的 git commit 会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。
但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen
Commitizen 是一个帮助我们编写规范 commit message 的工具;
1.安装
commitizen
npm install commitizen -D
2.安装cz-conventional-changelog
,并且初始化 cz-conventional-changelog:
npx commitizen init cz-conventional-changelog --save-dev --save-exact
这个命令会帮助我们安装 cz-conventional-changelog:
并且在 package.json 中进行配置:
这个时候我们提交代码需要使用 npx cz
:
- 第一步是选择 type,本次更新的类型
Type | 作用 |
---|---|
feat | 新增特性 (feature) |
fix | 修复 Bug(bug fix) |
docs | 修改文档 (documentation) |
style | 代码格式修改(white-space, formatting, missing semi colons, etc) |
refactor | 代码重构(refactor) |
perf | 改善性能(A code change that improves performance) |
test | 测试(when adding missing tests) |
build | 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等) |
ci | 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等 |
chore | 变更构建流程或辅助工具(比如更改测试环境) |
revert | 代码回退 |
- 第二步选择本次修改的范围(作用域)
? What is the scope of this change (e.g. component or file name): (press enter to skip) git
- 第三步选择提交的信息
? Write a short, imperative tense description of the change (max 89 chars): 安装了husky
- 第四步提交详细的描述信息
? Provide a longer description of the change: (press enter to skip)
- 第五步是否是一次重大的更改
? Are there any breaking changes? (y/N) n
- 第六步是否影响某个 open issue
? Does this change affect any open issues? (y/N) n
我们也可以在 scripts 中构建一个命令来执行 cz:
代码提交验证
如果我们按照 cz 来规范了提交风格,但是依然有同事通过 git commit
按照不规范的格式提交应该怎么办呢?
我们可以通过 commitlint 来限制提交;
1.安装
@commitlint/config-conventional
和@commitlint/cli
npm i @commitlint/config-conventional @commitlint/cli -D
2.在根目录创建 commitlint.config.js 文件,配置 commitlint
module.exports = {
extends: ['@commitlint/config-conventional']
}
3.使用 husky 生成 commit-msg 文件,验证提交信息:
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
第三方库集成
Router
基础配置
1、安装
npm i react-router-dom
npm i @types/react-router-dom
2、配置路由
位置:@/router/index.tsx
3、使用配置的路由
4、在index.tsx
中使用<HashRouter>
包裹<App>
组件和 props 类型约束
写法一: 直接给 props 添加类型约束
写法二: 给组件添加类型约束【推荐】
使用: 使用时会有类型约束
改变:
- 早期时,在组件中的 jsx 代码可以通过
组件.children
来接收,并且不需要事先在 props 类型中自己定义 - 现在,依然可以通过
组件.children
接收 jsx 代码,但是需要在 props 的类型中自己定义一个可选的 children
性能优化: 在导出组件时使用memo()
高阶函数包裹
React 组件模板片段
1、模板
2、VSCode 配置位置:文件 - 首选项 - 配置用户代码片段 - typescriptreact
路由-详细配置
1、基础配置、路由重定向
2、路由懒加载
- router 中: 使用 lazy、import 函数导入组件
- App 中: 使用
<Suspense>
对useRoutes(routes)
进行包裹,保证在下载组件时有占位组件
3、路由链接
路由-二级路由
1、基础配置
2、路由重定向
3、在一级路由组件中添加路由占位<Outlet>
4、二级导航
问题:点击 Link 时会闪一下
问题: 在点击 Link 的时候页面会闪一下
原因: 路由中的组件是懒加载的,所以会有一个下载的过程,此时就会使用占位的<Suspense fallback="">
组件,由于 fallback 值为"",所以就会闪一下空白页面
解决: 在二级路由的占位中同样使用<Suspense fallback="">
,可以缩小闪的范围
Redux
注意: 此处使用@reduxjs/toolkit
工具来管理 redux
安装插件
1、安装@reduxjs/toolkit
npm i @reduxjs/toolkit
2、安装react-redux
npm i react-redux
创建 store
1、创建 store,并挂载 reducer
2、挂载 store 给组件
3、创建 reducer
4、在组件中获取 state
5、定义 action
7、在组件中调用 action,修改 state
封装 useAppSelector
- 在 store 中获取
store.getState
的返回值类型 - 使用
useSelector
的增强函数代替useSelector
- 通过
TypedUseSelectorHook<>
为增强函数添加类型,并传递 store 的返回值类型给它
- 在组件中使用
useAppSelector
获取 state 中的值时就可以有类型推导了
封装 useAppDispatch
在组件中使用useAppDispatch
store 异步请求数据方式
方式一: 每次请求单独封装到一个函数中
方式二: 页面多个请求封装到一个函数中
注意: 此处不要用 await/async 会堵塞请求
Axios
安装
npm i axios
测试网络请求
区分开发、生产环境
1、手动切换
2、依赖当前环境
- process.env.NODE_ENV: 获取当前项目运行环境是 development 还是 production
3、依赖配置文件
- .env.production:生产环境
- .env.development:开发环境
1、定义 BASE_URL
格式: REACTAPP**XXX =
2、加载配置文件
补充:增加 REACT_APP_XXX 的 TS 类型提示
在react-app-env.d.ts
文件中添加如下:
styled-components
安装
npm i styled-components -D
类型声明
使用库的时候会遇到 TS 类型声明的问题,类型声明有以下几种方法:
- TS 内置的类型声明:DOM 等
- 第三方库
- 第三方库内部已经定义了类型声明:axios
- 存放在
@types/xxx
中的类型声明:需要另外安装:@types/react
、@types/react-dom
、@types/styled-components
- 自己写类型声明:lodash(现在在
@types
中也能找到类型声明了)
npm i @types/styled-components -D
*注意:*目前styled-components
已经内置了类型声明(2023-12-4)
定义样式组件
使用样式组件
Ant Design
安装
npm i antd
引入样式
在 css/index.less 中引入 antd.less
@import '~antd/dist/antd.less' // 自定义的样式;;
配置主题
使用组件
安装图标库
npm i @ant-design/icons
App
组件:AppHeader
使用组件
页面布局
1、定义样式HeaderLeft
、 HeaderRight
2、logo
3、导航栏
1、定义数据
2、遍历导航栏:区分 link 和 path
样式
4、分割线
5、右侧搭建
6、导入 AntDesign
见:导入 AntDesign
样式
设置 CSS 样式
见:styled-components
1、定义样式组件
2、使用样式组件
定义公共样式
定义公共样式的方法:
- 1、将公共样式抽取为公共类
- 2、使用混入定义公共样式
1、将公共样式抽取为公共类
2、使用混入定义公共样式
1、在 assets/theme/index.ts 中定义主题样式
2、在 index.ts 中挂载 theme 到组件树中
3、在组件的样式文件中使用 theme 的混入
记住当前选中状态
方法一:定义组件内部的状态
缺点:页面刷新后状态会丢失,需要另外匹配 path 才能解决
方法二:使用 NavLink,可以自动记录 active 状态
补充: NavLink 可以修改默认的 className,由 active 改成自定义的类名
组件:AppFooter
使用组件
页面布局
组件:AreaHeaderV1
页面布局
设置样式
定制化封装
1、通过在组件中接收属性 prop 来定制
2、使用定制化组件
组件:PlaylistItem
页面布局
使用组件
设置样式
组件:NewAlbumItem
使用组件
页面布局
设置样式
组件:AsideHeader
使用组件
const SettledSinger: FC<IProps> = () => {
return (
<SettledSingerCss>
+ <AsideHeader title="入驻歌手" moreText="查看全部 >" moreLink="#/discover/artist" />
</SettledSingerCss>
)
}
页面布局
interface IProps {
children?: ReactNode
title?: string
moreText?: string
moreLink?: string
}
const AsideHeader: FC<IProps> = (props) => {
const { title = '默认标题', moreText, moreLink } = props
return (
<AsideHeaderCss>
<h3 className="title">{title}</h3>
{moreText && moreLink && (
<div className="more">
<a href={moreLink} className="text">
{moreText}
</a>
</div>
)}
</AsideHeaderCss>
)
}
设置样式
export const AsideHeaderCss = styled.div`
display: flex;
justify-content: space-between;
border-bottom: 1px solid #d3d3d3;
height: 24px;
margin: 0 20px;
.title {
font-weight: 700;
font-size: 12px;
}
`
Discover
组件:NavBar
使用组件
页面布局
1、定义数据
2、遍历数据
3、样式
1、居中对齐
2、其他样式
Recommed
数据请求结构
结构一: 按照功能划分文件夹结构
- request
- service
- store
- component
结构二: 按照业务划分文件夹结构
- request
- component
- service
- store
组件:TopBanner
请求数据
1、在recommend/service/recommend.ts
中,封装网络请求
2、在recommend/store/recommend.ts
中,封装 recommendSlice,发送异步请求
方式一:
方式二:
3、在store/index.ts
中注册 recommendReducer
4、在组件中发送 action
使用组件
页面布局
1、基本布局
2、从 store 中获取数据(在 useAppSelector 中添加 shallowEqual)
3、整体布局
4、BannerLeft:轮播图
5、BannerRight:下载客户端
6、BannerControl:左右控制按钮
设置样式
1、整体布局
2、BannerWrapper
3、BannerLeft
4、BannerRight
5、BannerControl
轮播控制按钮
1、监听按钮点击
2、绑定页面元素的 Ref,调用其方法next()
prev()
3、绑定组件元素时,需要指定该组件的类型
4、在事件处理函数中,调用其内部方法实现轮播图滚动效果
5、滚动效果为淡入淡出
设置背景图
1、监听 Carousel 组建的 afterChange 事件
2、在事件处理函数中获取当前的图片
3、将在组件中获取的背景图地址,设置到 background 上
自定义指示器
1、隐藏原版指示器
2、指示器结构
3、安装classnames
npm i classnames
4、动态添加 active
5、调整指示器切换时机
Content
页面布局
设置样式
组件:HotRecommend
使用组件
页面布局
1、使用公共 header 组件
AreaHeaderV1
请求数据
1、service
2、store
3、组件中
展示数据
1、在组件中获取数据
2、遍历数据
3、使用组件 PlaylistItem
4、设置样式
格式化数字
1、在utils/format.ts
中封装格式化函数
2、使用函数格式化数字
格式化图片大小
1、在utils/format.ts
中封装格式化函数
2、使用函数格式化图片大小
组件:NewAlbum
使用组件
页面布局
设置样式
轮播图布局
1、页面布局
2、监听点击按钮,控制轮播
3、获取组件的 Ref
4、事件处理函数
5、隐藏指示器
6、调整切换速度
请求轮播图数据
1、service
2、store
3、组件
展示轮播图数据
1、遍历数据+分页
2、使用公共组件
组件:RecRanking
接口
- 飙升榜:
/playlist/detail?id=19723756
- 原创榜:
/playlist/detail?id=2884035
- 新歌榜:
/playlist/detail?id=3779629
使用组件
页面布局
设置样式
请求数据
2、service
3、store
注意: 此处将 3 个榜单数据统一放入一个数组中便于管理遍历
- 定义数据类型约束
- 定义修改 rankings 数据的 reducer
- 定义异步数据请求函数
注意: 将 3 个异步请求放入一个数组中统一管理需要保障以下 2 点:
- 1、获取到所有的结果后才进行 dispatch 操作
- 2、获取到的结果需要按照规定的顺序保存
实现思路: 可以通过Promise.all()
实现上面的要求
4、组件
展示数据
1、页面布局
2、设置样式
组件:RecRankingItem
1、使用组件
2、页面布局
header
list
footer
3、设置样式
header
hover 状态
list
footer
组件:RecLogin
使用组件
return (
<RecommendCss>
<Banner />
<div className="content wrap">
<div className="main">
<HotRecommed />
<NewAlbum />
<RecRanking />
</div>
<div className="aside">
+ <RecLogin />
<SettledSinger />
<HotAnchor />
</div>
</div>
</RecommendCss>
)
页面布局
const RecLogin: FC<IProps> = () => {
return (
<RecLoginCss className="sprite_02">
<p className="note">登录网易云音乐,可以享受无限收藏的乐趣,并且无限同步到手机</p>
<a href="#/discover/login" className="btn sprite_02">
用户登录
</a>
</RecLoginCss>
)
}
设置样式
export const RecLoginCss = styled.div`
width: 250px;
height: 126px;
background-position: 0 0;
.note {
width: 205px;
margin: 0 auto;
padding: 16px 0;
line-height: 22px;
}
.btn {
display: block;
margin: 0 auto;
width: 100px;
height: 31px;
line-height: 31px;
text-align: center;
color: #fff;
background-position: 0px -195px;
&:hover {
background-position: -110px -195px;
}
}
`
组件:SettledSinger
接口
接口:/artist/list
说明: 获取歌手分类列表
参数:
limit
,返回数量 , 默认为 30offset
,偏移数量,用于分页initial
,按首字母索引查找参数type
,分类:-1:全部 1:男歌手 2:女歌手 3:乐队area
,地区:-1:全部 7:华语 96:欧美 8:日本 16:韩国 0:其他
示例:
/artist/list?type=1&area=96&initial=b
/artist/list?type=2&area=2&initial=b
使用组件
return (
<RecommendCss>
<Banner />
<div className="content wrap">
<div className="main">
<HotRecommed />
<NewAlbum />
<RecRanking />
</div>
<div className="aside">
<RecLogin />
+ <SettledSinger />
<HotAnchor />
</div>
</div>
</RecommendCss>
)
页面布局
设置样式
请求数据
1、service
2、store
异步请求
reducer
state
类型约束
3、组件展示
组件:HotAnchor
接口
接口:/dj/hot
说明 : 获取热门电台
参数:
limit
,返回数量 , 默认为 30offset
,偏移数量,用于分页 , 如 😦 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0
示例:/dj/hot
使用组件
<RecommendCss>
<Banner />
<div className="content wrap">
<div className="main">
<HotRecommed />
<NewAlbum />
<RecRanking />
</div>
<div className="aside">
<RecLogin />
<SettledSinger />
+ <HotAnchor />
</div>
</div>
</RecommendCss>
页面布局
设置样式
请求数据
service
/* 请求热门主播数据 */
export function fetchHotDj(limit: number) {
return mrRequest.get({
url: '/dj/hot',
params: {
limit
}
})
}
store
export const fetchHotDjAction = createAsyncThunk(
'recommend/hotDj',
async (payload, { dispatch }) => {
const res = await fetchHotDj(5)
dispatch(getHotDjs(res.djRadios))
}
)
interface IStateRecommed {
+ hotDjs: any[]
}
const initialState: IStateRecommed = {
+ hotDjs: []
}
const recommendSlice = createSlice({
name: 'recommend',
initialState,
reducers: {
+ getHotDjs(state, { payload }) {
+ state.hotDjs = payload
+ }
}
})
export const { getHotDjs } = recommendSlice.actions
export default recommendSlice.reducer
组件
const Recommend: FC<IProps> = () => {
const dispatch = useDispatchTs()
// hook
useEffect(() => {
;+dispatch(fetchHotDjAction())
}, [])
}
Player
接口
使用组件
页面布局
设置样式
请求数据
组件:AppPlayerBar
接口
获取歌曲详情
接口: /song/detail
**说明:**调用此接口 , 传入音乐 id(支持多个 id, 用 ,
隔开), 可获得歌曲详情(dt 为歌曲时长)
参数:
ids
,(必选)音乐 id, 如ids=347230
示例:
/song/detail?ids=347230
/song/detail?ids=347230,347231
**返回数据:**常用数据
name
: String, 歌曲标题id
: u64, 歌曲 IDar
: Vec<Artist>
, 歌手列表fee
: enum,0: 免费或无版权 1: VIP 歌曲 4: 购买专辑 8: 非会员可免费播放低音质,会员可播放高音质及下载al
: Album, 专辑,如果是 DJ 节目(dj_type != 0)或者无专辑信息(single == 1),则专辑 id 为 0dt
: u64, 歌曲时长mv
: u64, 非零表示有 MV ID
音乐播放地址
https://music.163.com/song/media/outer/url?id=${id}.mp3
获取歌词
接口: /lyric
说明: 传入音乐 id 可获得对应音乐的歌词 ( 不需要登录 )
参数:
id
,必选,音乐 id
示例: /lyric?id=408332757
使用组件
页面布局
设置样式
请求歌曲数据
注意: 由于正在播放的歌曲是一个所有页面都可以访问的数据,最好是通过定义一个专门的 player store 处理相关的数据
playerSlice
store
展示数据
播放歌曲
1、绑定audio
DOM 元素
2、在useEffect
中执行 DOM 方法
此处的用处:当用户切换歌曲时可以自动播放
3、封装音乐播放地址函数
4、此时会报以下错误
错误原因:chrome 浏览器从 67+版本开始禁止了进入页面自动播放歌曲的功能,必须等用户手动点击播放才能播放歌曲
5、监听播放按钮点击,播放歌曲
6、根据播放状态显示播放、暂停按钮
7、点击播放按钮,切换播放、暂停
修改播放进度
播放进度条是通过 antd 的Slider
组件显示
1、定义播放进度值
2、获取音乐总时长(ms)
3、监听audio
的onTimeUpdate
事件,通过 audio 的currentTime
(单位:s)属性获取实时播放进度
4、此时进度条只会 1 秒动一次,有点慢。可以通过设置<Slider>
的step
属性为0.2
(单位:s)改变速度
5、隐藏tooltip
格式化时间
1、定义格式化时间函数formatTime()
2、展示总时长
3、展示实时播放时间
4、格式化实时播放时间
点击改变播放进度
1、点击播放条,通过<Slider>
的onAfterChange
事件改变value
值
2、获取、设置当前播放时间currentTime
3、更新界面显示
拖拽改变播放进度
1、监听<Slider>
的onChange
事件
2、由于拖拽和 audio 的onTimeUpdate
事件都在改变播放条进度,需要在拖拽时停止在onTimeUpdate
事件中改变播放条进度
2.1、isSliding
:记录当前是否处于拖拽状态
2.2、当处于拖拽事件中时,设置isSliding = true
2.3、只有当不处于拖拽状态时,才去修改progress
和currentTime
2.4、当松开拖拽时,设置isSliding = false
3、拖拽时,实时改变当前拖拽的时间
请求歌词数据
1、在 service 发起请求
2、在 store 中发起异步请求
3、解析获取到的歌词字符,获取播放时间、歌词
4、保存到 state 中
5、在组件中根据当前播放时间展示对应的歌词
6、优化: 当匹配到当前歌词后,不再一直匹配当前歌词
保存匹配到的索引 index 到 store 中
7、展示歌词:使用 antd 中的 message
展示歌词
key
,保证同时只存在一个 messageduration: 0
,取消默认的 3 秒关闭
重写 message 样式
播放列表
1、定义 store 中的变量
2.1、如果正在播放的歌曲不在播放列表中,需要先加入播放列表
2.2、如果正在播放的歌曲已经存在于播放列表中,只需取出该歌曲直接播放,不用再发送请求获取该歌曲的数据
歌曲切换
1、在 store 中定义playMode
记录歌曲播放模式(0 顺序,1 随机,2 单曲 。。。)
2、在组件中根据不同的播放模式展示不同的图标
3、点击模式图标,切换播放模式
4、点击上一首、下一首切换歌曲
在 store 中定义异步请求
5、同步切换歌词
播完自动下一首
1、监听onEnded
事件
2、切换歌曲
API
Antd
Slider
<Slider>
:返回:
,滑动输入条- 属性
- value?:
number
,设置当前取值。 - step?:
number
,步长,取值必须大于 0,并且可被 (max - min) 整除。默认 1 - 方法
- onChange:
(value) => void
,当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入 - onAfterChange:
(value) => void
,与mouseup
和keyup
触发时机一致,把当前值作为参数传入
HTML
audio
<audio>
:返回:
,用于在文档中嵌入音频内容。- 属性
- autoplay:
boolean
,自动播放 - controls:
boolean
,显示包含声音,播放进度,播放暂停的控制面板 - currentTime:
浮点数
,当前音频的播放位置(单位:s) - duration:
浮点数
,当前音频的总长度(单位:s) - loop:
boolean
,循环播放 - muted:
boolean
,静音 - src:
string
,嵌入的音频的 URL - 方法
- play():``,播放开始。
- pause():``,播放暂停。
- 事件
- onTimeUpdate:
() => void
,由currentTime
指定的时间更新。 - volumnChange():
() => void
,音量变化。 - onEnded:
() => void
,歌曲播放结束。
TS 类型约束
React 中的常用 TS 类型
在 React 中,使用 TypeScript(TS)可以提供更强大的类型检查和代码提示功能。下面是 React 中常用的一些 TS 类型的介绍:
React.FC:React 函数组件的类型。使用 React.FC 可以定义一个函数组件,并指定其 props 类型。例如:
tsxtsxCopy Codeimport React from 'react'; type MyComponentProps = { name: string; age: number; }; const MyComponent: React.FC<MyComponentProps> = ({ name, age }) => { return <div>{name} is {age} years old.</div>; };
React.ComponentProps:获取组件的 props 类型。可以使用 React.ComponentProps 获取特定组件的 props 类型,例如:
tsxtsxCopy Codeimport React from 'react'; type MyComponentProps = { name: string; age: number; }; const MyComponent: React.FC<MyComponentProps> = ({ name, age }) => { return <div>{name} is {age} years old.</div>; }; type MyComponentPropsType = React.ComponentProps<typeof MyComponent>; // MyComponentPropsType 的类型为:{ name: string; age: number; }
React.Ref:引用类型。可以使用 React.Ref 来定义引用类型,用于获取或设置组件的引用。例如:
tsxtsxCopy Codeimport React, { useRef } from 'react'; type InputRef = React.Ref<HTMLInputElement>; const MyComponent: React.FC = () => { const inputRef: InputRef = useRef(null); const handleClick = () => { if (inputRef.current) { inputRef.current.focus(); } }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={handleClick}>Focus</button> </div> ); };
React.ReactNode:React 节点类型。React.ReactNode 可以表示一个 React 组件、DOM 元素或其他 React 节点类型的值。例如:
tsxtsxCopy Codeimport React from 'react'; type MyComponentProps = { children: React.ReactNode; }; const MyComponent: React.FC<MyComponentProps> = ({ children }) => { return <div>{children}</div>; };
这些是 React 中常用的一些 TS 类型,在使用 React 开发中,使用这些类型可以提高代码的可读性和可维护性,并减少潜在的类型错误。
函数调用签名和调用泛型结合
函数调用签名-基础
~~
函数调用签名-参数为函数类型
函数调用签名-函数调用签名和调用泛型结合
函数式组件的 TS 类型
见:组件和 props 类型约束
类组件的 TS 类型
约束 props
约束 state
优化: 可以省去 constructor
redux 中额外类型约束
1、对于有些没有办法推导出来的类型,可以通过以下方法,手动定义类型
2、指定 actions 中的 payload 的类型