Skip to content

React18+TS-项目: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 创建项目的缺点

image-20230615172628584

2、推荐如下创建

创建 TS 项目时添加参数:--template typesc

sh
create-react-app mr-react18-ts-music --template typescript

初始化

1、删除多余的文件

image-20230615174720674

2、index.tsx

image-20230615175131087

3、App.tsx

html
import React from "react"; function App() { return
<div className="App">App</div>
; } export default App;

项目配置

项目 icon

替换原本的 favicon.ico

image-20230615175532098

项目标题

image-20230615175646907

项目别名等

1、安装插件:craco

sh
npm i @craco/craco@alpha -D

2、修改package.json中的 scripts

image-20230615181831906

3、创建craco.config.js ,配置 webpack 别名

js
const path = require("path");

+ const resolve = (dir) => path.resolve(__dirname, dir);

module.exports = {
  webpack: {
    alias: {
+      "@": resolve("src"),
    },
  },
};

4、在tsconfig.json中配置

image-20230615181703677

目录结构

image-20230619170254157

重置 CSS 样式

normalize.css

  • 依赖包:normalize.css

1、安装:normalize.css

sh
npm i normalize.css

2、在main.ts 中导入

ts
import 'normalize.css'

reset.less

自定义重置:reset.less

css
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

css
body {
  font-size: 14px;
  font-family: '';
}

问题:

1、Vue: Vite 默认不能识别less文件,需要安装less

sh
npm i less -D

2、 React: webpack 默认不能识别less文件,需要安装craco-less

  • 安装craco-less
sh
# 由于版本不匹配,必须安装 craco-less@2.1.0-alpha.0 这个版本
npm i craco-less@2.1.0-alpha.0 -D
  • 配置craco.config.js

image-20230619172137816

代码规范

集成 editorconfig 配置

.editorconfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格

yaml
# 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

shell
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 表示不加;
json
{
  "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 FormatterPrettier - 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
shell
npm i eslint-plugin-prettier eslint-config-prettier -D

添加 prettier 插件:

json
  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 中添加如下代码:

    js
    module.exports = {
    +  rules: {
    +    '@typescript-eslint/no-unused-vars': 'off'
    +  }
    }

配置 React 中的 ESLint

1、手动安装 ESLint

sh
npm i eslint -D

2、配置

sh
npx eslint --init

配置时的选项

image-20230619162215993

3、修改运行环境,添加 node 环境

image-20230619162401651

image-20230619162438875

4、VScode 中 eslint 的配置【非必要】

位置:首选项 - 设置 - settings.json

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
shell
npm i eslint-plugin-prettier eslint-config-prettier -D

5.2、添加 prettier 插件:

json
  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/typescript/recommended",

+    "plugin:prettier/recommended"
  ],

4、手动修改 eslint 检测规则

  • 在出现提示的位置,复制出现的错误:@typescript-eslint/no-unused-vars

  • .eslintrc.cjs 中添加如下代码:

    js
    module.exports = {
    +  rules: {
    +    '@typescript-eslint/no-unused-vars': 'off'
    +  }
    }

git-Husky 和 eslint

虽然我们已经要求项目使用 eslint 了,但是不能保证组员提交代码之前都将 eslint 中的问题解决掉了:

  • 也就是我们希望保证代码仓库中的代码都是符合 eslint 规范的;

  • 那么我们需要在组员执行 git commit 命令的时候对其进行校验,如果不符合 eslint 规范,那么自动通过规范进行修复;

那么如何做到这一点呢?可以通过 Husky 工具:

  • husky是一个 git hook 工具,可以帮助我们触发 git 提交的各个阶段:pre-commitcommit-msgpre-push

如何使用 husky 呢?

这里我们可以使用自动配置命令

shell
npx husky-init && npm install

注意: 在 windows 的 powershell 中需要给&&添加引号

sh
npx husky-init '&&' npm install

这里会做三件事:

1.安装 husky 相关的依赖:

image-20230615162857405

2.在项目目录下创建 .husky 文件夹:

npx huksy install

image-20230615162908690

3.在 package.json 中添加一个脚本:

image-20230615162943102

接下来,我们需要去完成一个操作:在进行 commit 时,执行 lint 脚本:

image-20230615163757848

这个时候我们执行 git commit 的时候会自动对代码进行 lint 校验。

git-commit 规范

代码提交风格

通常我们的 git commit 会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。

image-20230615164118256

但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen

  • Commitizen 是一个帮助我们编写规范 commit message 的工具;

    1.安装commitizen

shell
npm install commitizen -D

2.安装cz-conventional-changelog,并且初始化 cz-conventional-changelog:

shell
npx commitizen init cz-conventional-changelog --save-dev --save-exact

这个命令会帮助我们安装 cz-conventional-changelog:

image-20230615164205198

并且在 package.json 中进行配置:

image-20230615164358905

这个时候我们提交代码需要使用 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代码回退
  • 第二步选择本次修改的范围(作用域)
sh
? What is the scope of this change (e.g. component or file name): (press enter to skip) git
  • 第三步选择提交的信息
sh
? Write a short, imperative tense description of the change (max 89 chars): 安装了husky
  • 第四步提交详细的描述信息
sh
? Provide a longer description of the change: (press enter to skip)
  • 第五步是否是一次重大的更改
sh
? Are there any breaking changes? (y/N) n
  • 第六步是否影响某个 open issue
sh
? Does this change affect any open issues? (y/N) n

我们也可以在 scripts 中构建一个命令来执行 cz:

image-20230615165454050

代码提交验证

如果我们按照 cz 来规范了提交风格,但是依然有同事通过 git commit 按照不规范的格式提交应该怎么办呢?

  • 我们可以通过 commitlint 来限制提交;

    1.安装 @commitlint/config-conventional@commitlint/cli

shell
npm i @commitlint/config-conventional @commitlint/cli -D

2.在根目录创建 commitlint.config.js 文件,配置 commitlint

js
module.exports = {
  extends: ['@commitlint/config-conventional']
}

3.使用 husky 生成 commit-msg 文件,验证提交信息:

shell
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

第三方库集成

Router

基础配置

1、安装

sh
npm i react-router-dom

npm i @types/react-router-dom

2、配置路由

位置:@/router/index.tsx

image-20230619174025090

3、使用配置的路由

image-20230619173413157

4、在index.tsx中使用<HashRouter>包裹<App>

image-20230619174257380

组件和 props 类型约束

写法一: 直接给 props 添加类型约束

image-20230619175857901

写法二: 给组件添加类型约束【推荐】

image-20230619180001516

使用: 使用时会有类型约束

image-20230619180031560

改变:

  • 早期时,在组件中的 jsx 代码可以通过组件.children来接收,并且不需要事先在 props 类型中自己定义
  • 现在,依然可以通过组件.children接收 jsx 代码,但是需要在 props 的类型中自己定义一个可选的 children

image-20230619181103352

image-20230619181200286

image-20230619181214554

性能优化: 在导出组件时使用memo() 高阶函数包裹

image-20230619181705741

React 组件模板片段

1、模板

image-20230621121611634

image-20230621121725803

2、VSCode 配置位置:文件 - 首选项 - 配置用户代码片段 - typescriptreact

路由-详细配置

1、基础配置、路由重定向

image-20230621122642120

2、路由懒加载

  • router 中: 使用 lazy、import 函数导入组件

image-20230621123155393

  • App 中: 使用<Suspense>useRoutes(routes)进行包裹,保证在下载组件时有占位组件

image-20230621123506346

3、路由链接

image-20230621123656604

路由-二级路由

1、基础配置

image-20230621124836163

2、路由重定向

image-20230621124950807

3、在一级路由组件中添加路由占位<Outlet>

image-20230621125306823

4、二级导航

image-20230621125644407

问题: 在点击 Link 的时候页面会闪一下

原因: 路由中的组件是懒加载的,所以会有一个下载的过程,此时就会使用占位的<Suspense fallback=""> 组件,由于 fallback 值为"",所以就会闪一下空白页面

解决: 在二级路由的占位中同样使用<Suspense fallback="">,可以缩小闪的范围

image-20230621130347701

Redux

注意: 此处使用@reduxjs/toolkit工具来管理 redux

安装插件

1、安装@reduxjs/toolkit

sh
npm i @reduxjs/toolkit

2、安装react-redux

sh
npm i react-redux

创建 store

1、创建 store,并挂载 reducer

image-20230621132009517

2、挂载 store 给组件

image-20230621131411446

3、创建 reducer

image-20230621132012919

4、在组件中获取 state

image-20230621132220531

5、定义 action

image-20230621151113910

7、在组件中调用 action,修改 state

image-20230621151422457

封装 useAppSelector

  • 在 store 中获取store.getState的返回值类型
  • 使用useSelector的增强函数代替useSelector
  • 通过TypedUseSelectorHook<> 为增强函数添加类型,并传递 store 的返回值类型给它

image-20230621134523124

  • 在组件中使用useAppSelector获取 state 中的值时就可以有类型推导了

image-20230621135025376

封装 useAppDispatch

image-20230621151959783

在组件中使用useAppDispatch

image-20230621152117312

store 异步请求数据方式

方式一: 每次请求单独封装到一个函数中

image-20231205180230845

方式二: 页面多个请求封装到一个函数中

注意: 此处不要用 await/async 会堵塞请求

image-20231205180657509

Axios

安装

sh
npm i axios

测试网络请求

image-20230621164412414

image-20230621164340256

区分开发、生产环境

1、手动切换

image-20230621164738853

2、依赖当前环境

  • process.env.NODE_ENV: 获取当前项目运行环境是 development 还是 production

image-20230621165553181

3、依赖配置文件

  • .env.production:生产环境
  • .env.development:开发环境

1、定义 BASE_URL

格式: REACTAPP**XXX =

image-20230621170113327

2、加载配置文件

image-20230621171119526

补充:增加 REACT_APP_XXX 的 TS 类型提示

react-app-env.d.ts文件中添加如下:

image-20230728222422271

styled-components

安装

sh
npm i styled-components -D

类型声明

使用库的时候会遇到 TS 类型声明的问题,类型声明有以下几种方法:

  • TS 内置的类型声明:DOM 等
  • 第三方库
    • 第三方库内部已经定义了类型声明:axios
    • 存放在@types/xxx中的类型声明:需要另外安装:@types/react@types/react-dom@types/styled-components
    • 自己写类型声明:lodash(现在在@types中也能找到类型声明了)
sh
npm i @types/styled-components -D

*注意:*目前styled-components已经内置了类型声明(2023-12-4)

定义样式组件

image-20230621181114612

使用样式组件

image-20230621180930139

Ant Design

安装

sh
npm i antd

引入样式

在 css/index.less 中引入 antd.less

less
@import '~antd/dist/antd.less' // 自定义的样式;;

配置主题

image-20230807171129528

使用组件

image-20230807171307275

image-20230807171325136

安装图标库

sh
npm i @ant-design/icons

App

组件:AppHeader

image-20230621180135229

使用组件

image-20230807153909898

页面布局

1、定义样式HeaderLeftHeaderRight

image-20230807161514238

image-20230807161908501

2、logo

image-20230807163302607

image-20230807163347508

3、导航栏

1、定义数据

image-20230807162322986

2、遍历导航栏:区分 link 和 path

image-20230807162551433

image-20230807163015782

image-20230807163150848

样式

image-20230807163629210

4、分割线

image-20230807165631994

image-20230807165607603

5、右侧搭建

image-20230807170126667

image-20230807170212691

6、导入 AntDesign

见:导入 AntDesign

image-20230807172018265

image-20230807172240134

样式

image-20230807172359963

设置 CSS 样式

见:styled-components

1、定义样式组件

image-20230621181114612

2、使用样式组件

image-20230807155507233

定义公共样式

定义公共样式的方法:

  • 1、将公共样式抽取为公共类
  • 2、使用混入定义公共样式

1、将公共样式抽取为公共类

image-20230807155439042

image-20230807155533915

2、使用混入定义公共样式

1、在 assets/theme/index.ts 中定义主题样式

image-20230807155831832

2、在 index.ts 中挂载 theme 到组件树中

image-20230807160052332

image-20230807160127986

image-20230807160119526

3、在组件的样式文件中使用 theme 的混入

image-20230807160306232

记住当前选中状态

方法一:定义组件内部的状态

缺点:页面刷新后状态会丢失,需要另外匹配 path 才能解决

image-20230807164203451

方法二:使用 NavLink,可以自动记录 active 状态

image-20230807164636345

补充: NavLink 可以修改默认的 className,由 active 改成自定义的类名

image-20230807165256430

image-20230807165317317

组件:AppFooter

使用组件

image-20230621175734199

页面布局

image-20230621180008299

组件:AreaHeaderV1

image-20230808152514576

页面布局

image-20230808153755531

设置样式

image-20230808153645873

定制化封装

1、通过在组件中接收属性 prop 来定制

image-20230808155007374

image-20230808155046028

2、使用定制化组件

image-20230808155339915

组件:PlaylistItem

页面布局

image-20230808164444151

使用组件

image-20230808163359320

设置样式

image-20230808164729245

组件:NewAlbumItem

使用组件

页面布局

image-20230808182402675

image-20230808182415066

image-20230808182108460

设置样式

组件:AsideHeader

使用组件

tsx
const SettledSinger: FC<IProps> = () => {
  return (
    <SettledSingerCss>
      + <AsideHeader title="入驻歌手" moreText="查看全部 &gt;" moreLink="#/discover/artist" />
    </SettledSingerCss>
  )
}

页面布局

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

设置样式

less
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

image-20230807173940591

使用组件

image-20230807173858503

页面布局

1、定义数据

image-20230807174204765

2、遍历数据

image-20230807174327815

image-20230807174703397

image-20230807175327145

3、样式

1、居中对齐

image-20230807175348399

2、其他样式

image-20230807175253449

Recommed

数据请求结构

结构一: 按照功能划分文件夹结构

  • request
  • service
  • store
  • component

结构二: 按照业务划分文件夹结构

  • request
  • component
    • service
    • store

组件:TopBanner

image-20230808111308644

请求数据

1、在recommend/service/recommend.ts中,封装网络请求

image-20230807181146866

2、在recommend/store/recommend.ts中,封装 recommendSlice,发送异步请求

  • 方式一: image-20230808104325421

  • 方式二: image-20230808105914521

3、在store/index.ts中注册 recommendReducer

image-20230807181630369

4、在组件中发送 action image-20230807182124910

使用组件

image-20230808110835898

页面布局

1、基本布局

image-20230808110810002

2、从 store 中获取数据(在 useAppSelector 中添加 shallowEqual)

image-20230808111102628

3、整体布局

image-20230808112829265

4、BannerLeft:轮播图

image-20230808113002139

image-20230808114313365

5、BannerRight:下载客户端

image-20230808114632182

6、BannerControl:左右控制按钮

image-20230808115023809

设置样式

1、整体布局

image-20230808112656629

2、BannerWrapper

image-20230808113825943

3、BannerLeft

image-20230808114229581

4、BannerRight

image-20230808114544528

5、BannerControl

image-20230808114820639

轮播控制按钮

1、监听按钮点击

image-20230808120016040

2、绑定页面元素的 Ref,调用其方法next() prev()

image-20230808115519888

image-20230808115607321

3、绑定组件元素时,需要指定该组件的类型

image-20230808120008087

image-20230808120100637

4、在事件处理函数中,调用其内部方法实现轮播图滚动效果

image-20230808120246294

5、滚动效果为淡入淡出

image-20230808120714222

设置背景图

1、监听 Carousel 组建的 afterChange 事件

image-20230808121201558

2、在事件处理函数中获取当前的图片

image-20230808121329705

image-20230808121624548

3、将在组件中获取的背景图地址,设置到 background 上

image-20230808121840481

自定义指示器

1、隐藏原版指示器

image-20230808142625831

2、指示器结构

image-20230808142829207

3、安装classnames

sh
npm i classnames

4、动态添加 active

image-20230808143214650

5、调整指示器切换时机

image-20230808143452830

Content

image-20230808150607466

页面布局

image-20230808151938388

设置样式

image-20230808151449513

组件:HotRecommend

使用组件

image-20230808152007191

页面布局

1、使用公共 header 组件

image-20230808152445118

AreaHeaderV1

image-20230808155444046

请求数据

1、service

image-20230808162154737

2、store

image-20230808162347985

image-20230808162255975

image-20230808162321581

3、组件中

image-20230808161329307

展示数据

1、在组件中获取数据

image-20230808162528814

2、遍历数据

image-20230808162824285

3、使用组件 PlaylistItem

image-20230808163359320

4、设置样式

image-20230808165036098

格式化数字

1、在utils/format.ts中封装格式化函数

image-20230808165713651

2、使用函数格式化数字

image-20230808170019161

格式化图片大小

1、在utils/format.ts中封装格式化函数

image-20230808170403569

2、使用函数格式化图片大小

image-20230808170423949

组件:NewAlbum

image-20230808171643715

使用组件

image-20230808171552993

页面布局

image-20230808172157104

设置样式

image-20230808172319583

轮播图布局

1、页面布局

image-20230808173338979

image-20230808173351643

2、监听点击按钮,控制轮播

image-20230808173511106

image-20230808173516782

3、获取组件的 Ref

image-20230808173706026

image-20230808173723710

4、事件处理函数

image-20230808173753638

5、隐藏指示器

image-20230808173831594

6、调整切换速度

image-20230808174104072

请求轮播图数据

1、service

image-20230808175329712

2、store

image-20230808175441944

3、组件

image-20230808175252297

展示轮播图数据

1、遍历数据+分页

image-20230808175824648

image-20230808180751352

2、使用公共组件

组件:RecRanking

image-20231205181423683

接口

  • 飙升榜:/playlist/detail?id=19723756
  • 原创榜:/playlist/detail?id=2884035
  • 新歌榜:/playlist/detail?id=3779629

使用组件

页面布局

设置样式

请求数据

2、service

3、store

注意: 此处将 3 个榜单数据统一放入一个数组中便于管理遍历

  • 定义数据类型约束
image-20231205210937152
  • 定义修改 rankings 数据的 reducer
image-20231205211046272
  • 定义异步数据请求函数

注意: 将 3 个异步请求放入一个数组中统一管理需要保障以下 2 点:

  • 1、获取到所有的结果后才进行 dispatch 操作
  • 2、获取到的结果需要按照规定的顺序保存

实现思路: 可以通过Promise.all()实现上面的要求

image-20231205211210239

4、组件

image-20231205204444009

展示数据

1、页面布局

image-20231205212709835

2、设置样式

image-20231205212832610

组件:RecRankingItem

1、使用组件

image-20231205213234819

2、页面布局

header

image-20231205214019822

list

image-20231205220053058

footer

image-20231205220239959

3、设置样式

image-20231205213039402

header

image-20231205214200909

hover 状态

image-20231205214516126

list

image-20231205215716500image-20231205215723954

footer

image-20231205220403694

组件:RecLogin

使用组件

tsx
return (
  <RecommendCss>
    <Banner />
    <div className="content wrap">
      <div className="main">
        <HotRecommed />
        <NewAlbum />
        <RecRanking />
      </div>
      <div className="aside">
        + <RecLogin />
        <SettledSinger />
        <HotAnchor />
      </div>
    </div>
  </RecommendCss>
)

页面布局

tsx
const RecLogin: FC<IProps> = () => {
  return (
    <RecLoginCss className="sprite_02">
      <p className="note">登录网易云音乐,可以享受无限收藏的乐趣,并且无限同步到手机</p>
      <a href="#/discover/login" className="btn sprite_02">
        用户登录
      </a>
    </RecLoginCss>
  )
}

设置样式

less
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,返回数量 , 默认为 30
  • offset,偏移数量,用于分页
  • 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

使用组件

tsx
return (
  <RecommendCss>
    <Banner />
    <div className="content wrap">
      <div className="main">
        <HotRecommed />
        <NewAlbum />
        <RecRanking />
      </div>
      <div className="aside">
        <RecLogin />
        + <SettledSinger />
        <HotAnchor />
      </div>
    </div>
  </RecommendCss>
)

页面布局

image-20231206114447525

设置样式

image-20231206114510277

请求数据


1、service

image-20231206113017031

2、store

异步请求

image-20231206113353461

reducer

image-20231206113304381

image-20231206113322358

state

image-20231206113237007

类型约束

image-20231206113245215

3、组件展示

image-20231206113507876

组件:HotAnchor

接口

接口/dj/hot

说明 : 获取热门电台

参数

  • limit,返回数量 , 默认为 30
  • offset,偏移数量,用于分页 , 如 😦 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0

示例/dj/hot

使用组件

tsx
<RecommendCss>
  <Banner />
  <div className="content wrap">
    <div className="main">
      <HotRecommed />
      <NewAlbum />
      <RecRanking />
    </div>
    <div className="aside">
      <RecLogin />
      <SettledSinger />
      + <HotAnchor />
    </div>
  </div>
</RecommendCss>

页面布局

image-20231206120415407

设置样式

image-20231206120618834

请求数据

service

ts
/* 请求热门主播数据 */
export function fetchHotDj(limit: number) {
  return mrRequest.get({
    url: '/dj/hot',
    params: {
      limit
    }
  })
}

store

ts
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

组件

tsx
const Recommend: FC<IProps> = () => {
  const dispatch = useDispatchTs()

  // hook
  useEffect(() => {
    ;+dispatch(fetchHotDjAction())
  }, [])
}

Player

接口

使用组件

页面布局

设置样式

请求数据

组件:AppPlayerBar

image-20231206153730454

接口

获取歌曲详情

接口: /song/detail

**说明:**调用此接口 , 传入音乐 id(支持多个 id, 用 , 隔开), 可获得歌曲详情(dt 为歌曲时长)

参数:

  • ids,(必选)音乐 id, 如 ids=347230

示例:

  • /song/detail?ids=347230
  • /song/detail?ids=347230,347231

**返回数据:**常用数据

  • name: String, 歌曲标题
  • id: u64, 歌曲 ID
  • ar: Vec <Artist>, 歌手列表
  • fee: enum,0: 免费或无版权 1: VIP 歌曲 4: 购买专辑 8: 非会员可免费播放低音质,会员可播放高音质及下载
  • al: Album, 专辑,如果是 DJ 节目(dj_type != 0)或者无专辑信息(single == 1),则专辑 id 为 0
  • dt: u64, 歌曲时长
  • mv: u64, 非零表示有 MV ID
音乐播放地址
  • https://music.163.com/song/media/outer/url?id=${id}.mp3
获取歌词

接口: /lyric

说明: 传入音乐 id 可获得对应音乐的歌词 ( 不需要登录 )

参数:

  • id,必选,音乐 id

示例: /lyric?id=408332757

使用组件

image-20231206153624641

页面布局

image-20231206160412103

image-20231206161450442

设置样式

image-20231206153924066

image-20231206154732687

image-20231206160552872

image-20231206160828131

image-20231206161558377

image-20231206161613210

请求歌曲数据

注意: 由于正在播放的歌曲是一个所有页面都可以访问的数据,最好是通过定义一个专门的 player store 处理相关的数据

playerSlice

image-20231207154746797

image-20231207154609135

store

image-20231206163617073

展示数据

image-20231206164420332

image-20231206164700653

播放歌曲

1、绑定audioDOM 元素

image-20231206172809777

image-20231206172841237

2、在useEffect中执行 DOM 方法

image-20231206175708460

此处的用处:当用户切换歌曲时可以自动播放

3、封装音乐播放地址函数

image-20231206173532302

4、此时会报以下错误

image-20231206174517313

错误原因:chrome 浏览器从 67+版本开始禁止了进入页面自动播放歌曲的功能,必须等用户手动点击播放才能播放歌曲

5、监听播放按钮点击,播放歌曲

image-20231206174808811

image-20231206174900184

6、根据播放状态显示播放、暂停按钮

image-20231206175515446

image-20231206175206226

image-20231206175308244

image-20231206175400592

7、点击播放按钮,切换播放、暂停

image-20231206175859008

修改播放进度

播放进度条是通过 antd 的Slider组件显示

1、定义播放进度值

image-20231206180440185

image-20231206180507278

2、获取音乐总时长(ms)

image-20231206180959283

image-20231206181117949

3、监听audioonTimeUpdate事件,通过 audio 的currentTime(单位:s)属性获取实时播放进度

image-20231206180613996

image-20231206181248723

4、此时进度条只会 1 秒动一次,有点慢。可以通过设置<Slider>step属性为0.2(单位:s)改变速度

image-20231206181633388

5、隐藏tooltip

image-20231206181658385

格式化时间

1、定义格式化时间函数formatTime()

image-20231207143746010

2、展示总时长

image-20231207143917044

3、展示实时播放时间

image-20231207143956296

image-20231207144013526

image-20231207144225463

4、格式化实时播放时间

image-20231207144252456

点击改变播放进度

1、点击播放条,通过<Slider>onAfterChange事件改变value

image-20231207144755199

2、获取、设置当前播放时间currentTime

image-20231207145326584

3、更新界面显示

image-20231207145334467

拖拽改变播放进度

1、监听<Slider>onChange事件

image-20231207145550596

2、由于拖拽和 audio 的onTimeUpdate事件都在改变播放条进度,需要在拖拽时停止在onTimeUpdate事件中改变播放条进度

2.1、isSliding :记录当前是否处于拖拽状态

image-20231207150307214

2.2、当处于拖拽事件中时,设置isSliding = true

image-20231207150401272

2.3、只有当不处于拖拽状态时,才去修改progresscurrentTime

image-20231207150547812

2.4、当松开拖拽时,设置isSliding = false

image-20231207150907693

3、拖拽时,实时改变当前拖拽的时间

image-20231207151118446

请求歌词数据

1、在 service 发起请求

image-20231207155618091

2、在 store 中发起异步请求

image-20231207160542089

3、解析获取到的歌词字符,获取播放时间、歌词

image-20231207162036266

4、保存到 state 中

image-20231207162208101

image-20231207162238209

image-20231207162314724

5、在组件中根据当前播放时间展示对应的歌词

image-20231207162524620

image-20231207164641000

6、优化: 当匹配到当前歌词后,不再一直匹配当前歌词

保存匹配到的索引 index 到 store 中

image-20231207164827829

image-20231207164852271

image-20231207165112435

7、展示歌词:使用 antd 中的 message展示歌词

  • key,保证同时只存在一个 message
  • duration: 0,取消默认的 3 秒关闭

image-20231207170136274

重写 message 样式

image-20231207170118265

播放列表

1、定义 store 中的变量

image-20231207171004893

2.1、如果正在播放的歌曲不在播放列表中,需要先加入播放列表

2.2、如果正在播放的歌曲已经存在于播放列表中,只需取出该歌曲直接播放,不用再发送请求获取该歌曲的数据

image-20231207173608650

歌曲切换

1、在 store 中定义playMode记录歌曲播放模式(0 顺序,1 随机,2 单曲 。。。)

image-20231207173914036

2、在组件中根据不同的播放模式展示不同的图标

image-20231207174015130

image-20231207174057640

image-20231207174132010

image-20231207174235903

3、点击模式图标,切换播放模式

image-20231207174320558

image-20231207174504773

image-20231207174450079

4、点击上一首、下一首切换歌曲

image-20231207181005369

image-20231207181259989

在 store 中定义异步请求

image-20231207181224338

image-20231208155924692

5、同步切换歌词

image-20231208161654764

播完自动下一首

1、监听onEnded事件

image-20231208162913707

2、切换歌曲

image-20231208163152358

API

Antd

Slider

  • <Slider>返回:,滑动输入条
    • 属性
    • value?number,设置当前取值。
    • step?number,步长,取值必须大于 0,并且可被 (max - min) 整除。默认 1
    • 方法
    • onChange(value) => void,当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入
    • onAfterChange(value) => void,与 mouseupkeyup 触发时机一致,把当前值作为参数传入

HTML

audio

  • <audio>返回:,用于在文档中嵌入音频内容。
    • 属性
    • autoplayboolean,自动播放
    • controlsboolean,显示包含声音,播放进度,播放暂停的控制面板
    • currentTime浮点数,当前音频的播放位置(单位:s)
    • duration浮点数,当前音频的总长度(单位:s)
    • loopboolean,循环播放
    • mutedboolean,静音
    • srcstring,嵌入的音频的 URL
    • 方法
    • play():``,播放开始。
    • pause():``,播放暂停。
    • 事件
    • onTimeUpdate() => void,由 currentTime 指定的时间更新。
    • volumnChange()() => void,音量变化。
    • onEnded() => void,歌曲播放结束。

TS 类型约束

React 中的常用 TS 类型

在 React 中,使用 TypeScript(TS)可以提供更强大的类型检查和代码提示功能。下面是 React 中常用的一些 TS 类型的介绍:

  1. React.FC:React 函数组件的类型。使用 React.FC 可以定义一个函数组件,并指定其 props 类型。例如:

    tsx
    tsxCopy 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>;
    };
  2. React.ComponentProps:获取组件的 props 类型。可以使用 React.ComponentProps 获取特定组件的 props 类型,例如:

    tsx
    tsxCopy 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; }
  3. React.Ref:引用类型。可以使用 React.Ref 来定义引用类型,用于获取或设置组件的引用。例如:

    tsx
    tsxCopy 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>
      );
    };
  4. React.ReactNode:React 节点类型。React.ReactNode 可以表示一个 React 组件、DOM 元素或其他 React 节点类型的值。例如:

    tsx
    tsxCopy Codeimport React from 'react';
    
    type MyComponentProps = {
      children: React.ReactNode;
    };
    
    const MyComponent: React.FC<MyComponentProps> = ({ children }) => {
      return <div>{children}</div>;
    };

这些是 React 中常用的一些 TS 类型,在使用 React 开发中,使用这些类型可以提高代码的可读性和可维护性,并减少潜在的类型错误。

函数调用签名和调用泛型结合

函数调用签名-基础

image-20230621153226124~~

函数调用签名-参数为函数类型

image-20230621153427589

函数调用签名-函数调用签名和调用泛型结合

image-20230621154238336

image-20230728172918365

函数式组件的 TS 类型

见:组件和 props 类型约束

类组件的 TS 类型

约束 props

image-20230621172016722

约束 state

image-20230621172500402

优化: 可以省去 constructor

image-20230807151436605

redux 中额外类型约束

1、对于有些没有办法推导出来的类型,可以通过以下方法,手动定义类型

image-20230621174313021

2、指定 actions 中的 payload 的类型

image-20230621174709562