因为react创建项目不像vue会提示需要按照xxx,react默认创建出的项目是不包含其他东西的,我们想集成typescript时,可以追加参数
create-react-app yh_music --template typescript
删掉一些无用文件变成下图
我们会发现一个文件
react-app-env.d.ts文件有什么作用呢?
此文件引用特定于使用Create React App启动的项目的TypeScript类型声明。
这些类型声明添加了对导入资源文件的支持,如bmp、gif、jpeg、jpg、png、webp和svg。这意味着以下导入将按预期正常工作,不会出现错误:
import logo from './logo.svg';
它还添加了对导入CSS模块的支持。这与导入扩展名为.module.css、.module.scss和.module.sass的文件有关。
取消严格模式
在index.jsx中可将React.StrictMode删除掉,避免子组件刷新两次
配置别名,使用craco修改webpack的配置
配置同爱彼迎项目
注意这里配完会报错,使用@符号后ts报错
在tsconfig.json文件加两个字段即可解决
一个是baseUrl,一个是path
EditorConfig: 专注于统一编辑器编码风格配置
Prettier: 专注于检查并自动更正代码风格,美化代码
Eslint: 专注于 JavaScript 代码质量检查, 编码风格约束等
Editconfig有助于为不同IDE编辑器上处理同一项目的多个开发人员提供维护一致的编码风格
npm install perttier -D
.editorconfig文件
# 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
.prettierrc文件
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"trailingComma": "none",
"semi": false
}
在我们配置完后使用ctrl+s保存时就会发现已经触发prettier的规则了
我们现在在package.json里的script里面加上下面这句代码
"prettier":"prettier --write ."
然后配置一个.prettierignore文件,因为上面的命令是让所有的代码都执行prettier的格式化,但是对于nodemodules一些文件不需要格式化,所以要配置忽略文件
.prettierignore
/build/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
执行npm run perttier就会都格式化了。
还需配置一个东西,安装这个插件
在vscode的文件-首选项-设置中搜索editor default
找到格式化配置
选择prettier
这样就配好了,我们使用ctrl+s保存时就会触发prettier的配置了
安装
npm install eslint -D
进行配置,但是手动配置太麻烦了,配置很多,这里采用自动配置
执行
npx eslint --init
提示下图,选择第二个To check syntax and find problems,检查语法并且找问题
下一个选择esmodule还是common js的模块化规范,一般是esmodule
如果用ts了会提示你安装ts的eslint依赖吗?一般选yes
然后就会生成一个.eslintrc.js文件
安装eslint插件
在vscode中 文件-首选项-设置中点击中间的按钮(鼠标悬停会提示打开设置的那个按钮)
在配置中加上
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.alwaysShowStatus": true,
"eslint.validate":[
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
配置eslint和prettier的冲突
安装
npm install eslint-plugin-prettier eslint-config-prettier -D
在.eslintrc.js中加上下面这句代码即可,意思检测eslint时同时检测prettier。
不这么配的话你在vs编辑器随便写不符合prettier的规范eslint不会报错,只会在编译的时候报错。
vue脚手架帮我们都搭好了,但是react没有,所以我们在从零搭建过程会有点繁琐
normalize.css配置
安装normalize.css
npm install normalize.css
然后在index.ts中写
import 'normalize.css'
reset.css配置
然后在assets文件夹下的css文件夹写一个less文件可以称为公共文件。
在index.ts中导入
注意:默认react不支持less那么安装
npm install @craco/craco@alpha -D
在craco.config.js中引入
const CracoLessPlugin = require('craco-less')
在module.exports导出的对象写
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true
}
}
}
}
],
index.tsx文件
import React from 'react'
import { Navigate } from 'react-router-dom'
import type { RouteObject } from 'react-router-dom'
const routes: RouteObject[] = [
{
path: '/',
element: <Navigate to="/discover" />
}
]
export default routes
首先在snippet creator上生成代码片段模板
在线snippet creator
然后复制我们需要快速生成的代码模板
这里将Home改为${Home}再复制到下面的区域中,改的原因是改了后会将光标自动定位到这个Home
输入触发片段的快捷代码
将生成的代码复制到VsCode的用户代码片段中
输入typescriptreact并选择
将代码复制到该文件中即可
然后输入快捷键即可发现新的代码段已经可以生成了
早期我们在IProps上面并不需要定义children属性,react默认帮我们带上了,但是现在我们需要手动指定children属性。如果不指定的话,意味着你在使用Discover组件时不能写插槽了。它的类型是一个ReatNode,因为我们不确定传递的是什么类型。
这是因为Suspense只做了对第一层路由的处理,当我们对二级路由也做懒加载时,我们二级路由没有用Suspense处理所以我们对第二层路由也要用Suspense包裹
下图是我们Discover页面,当对Discover页面的子组件也做懒加载时,需要用Suspense包裹一下占位元素Outlet
下载toolkit和react-redux
npm install @reduxjs/toolkit react-redux
然后在store配置
然后在index.ts中导入后使用provider包裹
组件中通过useselector来引入state
但是使用useselector报一个state为unknown的错误
解决
但是这样做每次都要写any,我们希望可以自动推导出来类型
在store的index.ts中自己写一个TypedUseSelectorHook
import {
useSelector,
useDispatch,
TypedUseSelectorHook,
shallowEqual
} from 'react-redux'
//拿到store.getState的类型
type GetStateFnType = typeof store.getState
//拿到store.getState函数的返回值类型
export type IRootState = ReturnType<GetStateFnType>
type DispatchType = typeof store.dispatch
// useAppSelector的hook,函数调用签名的方式
export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
export const useAppDispatch: () => DispatchType = useDispatch
export const shallowEqualApp = shallowEqual
这样使用TypedUseSelectorHook代替useSelector即可。
其实useDispatch和shallowEqual可以使用官方的也可以使用自己封装的
json接口数据转ts类型在线链接
定义好对应的数组类型
interface bannertype {
name: string
age: number
}
使用的时候通过泛型传入,表明banner的类型是bannertype[]
const [banner, setbanner] = useState<bannertype[]>([])
在PureComponent后传入泛型,第一个参数就是props的类型,第二个参数就是state的类型
类中可以简化constructor,直接不写,运用成员声明直接写state={ height: ‘1.88’,sex: ‘man’}简化this.state的写法
import React, { PureComponent } from 'react'
interface Iprops {
name: string
age: number
}
interface Istate {
height: string
sex: string
}
export default class a extends PureComponent<Iprops, Istate> {
state = {
height: '1.88',
sex: 'man'
}
// constructor(props: Iprops) {
// super(props)
// this.state = {
// height: '1.88',
// sex: 'man'
// }
// }
render() {
return (
<div>
<h1>{this.props.age}</h1>
<h1>{this.state.height}</h1>
</div>
)
}
}
我们install后引入报错
原因:没有进行类型声明
解决:
npm i --save-dev @types/styled-components
如果某个类或者一些精灵图类名我们使用的很多,我们可以不用再styled-components里面写了,直接将对应的类写到全局引入的common.css里面,然后在对应jsx元素上面加上类名就行。
common.css文件
使用NavLink组件即可
首先想使用组件带有的api函数,要绑定ref,这时候类型要怎么给呢?
引入类型和Carousel 组件
import type { FC, ReactNode, ElementRef } from 'react'
import { Carousel } from 'antd'
传入泛型,ElementRef又接收一个泛型
const bannerRef = useRef<ElementRef<typeof Carousel>>(null)
function handlePrevClick() {
bannerRef.current?.prev()
}
function handleNextClick() {
bannerRef.current?.next()
}
比如content选择器,代表这个RecommendWrapper的直接子元素,不会干扰嵌套中的.content类选择器的样式
在一个轮播图中展示五条数据,点击切换按钮切换到下五条
使用slice切割,item传入[0, 1],因为只有十条数据。
<Carousel ref={bannerRef} dots={false} speed={1500}>
{[0, 1].map((item) => {
return (
<div key={item}>
<div className="album-list">
{newAlbums.slice(item * 5, (item + 1) * 5).map((album) => {
return <NewAlbumItem key={album.id} itemData={album} />
})}
</div>
</div>
)
})}
</Carousel>
首先使用fixed定位将音乐播放器固定到屏幕下方,点击切换暂停播放的图标是设置一个isPlaying变量,传入styled-components控制图片的切换。音乐播放器内部就是一个进度条组件和一个原生radio html元素。
使用useAppSelector获取到歌曲信息,包含名称,时长等。
需求一:实现点击播放图标播放歌曲并使进度条缓慢移动
1.点击后控制isPlaying为true,切换图标
2.radio自带一个事件onTimeUpdate,这个函数在每次radio播放的每一刻都会执行,然后在这个函数内通过audioRef.current!.currentTime可以获取到当前radio的已播放时间,单位毫秒,然后将该时间设置为进度条的播放时间,然后计算出歌曲播放的百分比,赋值给进度条的进度即可。
需求二:实现点击进度条,歌曲实时切换进度,然后进度条缓慢移动继续播放
首先点击进度条会触发一个进度条组件自带事件handleSliderChanged,在这个函数内部我们可以获取到进度条的百分比,将百分比设置为进度条进度。进度条的百分比乘以总毫秒数得到当前时间,赋值给进度条的播放时间。
需求三:实现拖拽进度条,歌曲实时切换进度,然后进度条缓慢移动继续播放
首先拖拽进度条会触发一个进度条组件自带事件handleSliderChanging,首先我们要声明当前状态是拖拽的,可以有用一个state进行记录,在这个函数内部改变state为true,代表拖拽(防止一直触发onTimeUpdate事件改变进度条进度),其余同需求二步骤
需求四:歌曲播放时匹配对应歌词并显示
首先我们将获取的歌词数据解析为一个对象形式,这个对象内部包含开始播放这句歌词的毫秒,歌词的内容。
然后将存入redux中,在我们歌曲播放的时候会触发onTimeUpdate事件,在函数内部我们遍历歌词,如果歌词的毫秒数大于当前的毫秒,那么break,记录index,这样就找到了我们想要的歌词
需求五:避免匹配歌词时多次刷新展示歌词的组件
在onTimeUpdate函数匹配到歌词的index时,写一个判断,如果当前歌曲还在播放这句歌词,那么index不会变,然后1return掉就行,变了我们再存入redux中,这样就不会播放一句歌词时在redux中存入很多次index。
if (lyricIndex === index || index === -1) return
dispatch(changeLyricIndexAction(index))
需求六:歌曲和歌词的切换
首先我们在redux中存一个playSongList,代表所有的歌曲,再存一个playSongIndex代表当前播放的索引,当我们播放歌曲时,存入playSongList与playSongIndex,当我们切换下一首时,调用redux中的changeMusicAction,传入isNext参数,这个函数干的事就是先判断播放模式,然后是随机的话就随机一个newindex,从playSongList中获取一首新歌然后dispatch改变当前歌曲。如果是顺序播放并且isNext是true(代表下一首)就是将index+1,然后同上。然后歌词的切换就是调用接口获取最新的歌词保存到redux中
需求七:播放模式的切换
在redux中设计一个变量表示播放模式,在redux中进行保存,在图标的点击函数中切换redux的播放模式,将播放模式传入css中控制图标显示
audioRef.current!.src = getSongPlayUrl(currentSong.id)