react18-代码规范-ts-仿网易云项目

项目创建

因为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配置

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

然后安装这个插件
在这里插入图片描述

prettier配置

.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的配置了

eslint配置

安装
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没有,所以我们在从零搭建过程会有点繁琐

CSS样式的重置

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

生成含有ts的代码组件段,下次直接快捷键生成

首先在snippet creator上生成代码片段模板
在线snippet creator

然后复制我们需要快速生成的代码模板
在这里插入图片描述
这里将Home改为${Home}再复制到下面的区域中,改的原因是改了后会将光标自动定位到这个Home在这里插入图片描述
输入触发片段的快捷代码
在这里插入图片描述
将生成的代码复制到VsCode的用户代码片段中
在这里插入图片描述
输入typescriptreact并选择
在这里插入图片描述

将代码复制到该文件中即可
然后输入快捷键即可发现新的代码段已经可以生成了

props的chiildren属性

早期我们在IProps上面并不需要定义children属性,react默认帮我们带上了,但是现在我们需要手动指定children属性。如果不指定的话,意味着你在使用Discover组件时不能写插槽了。它的类型是一个ReatNode,因为我们不确定传递的是什么类型。
在这里插入图片描述

路由懒加载后切换二级路由组件,页面会闪一下

这是因为Suspense只做了对第一层路由的处理,当我们对二级路由也做懒加载时,我们二级路由没有用Suspense处理所以我们对第二层路由也要用Suspense包裹
下图是我们Discover页面,当对Discover页面的子组件也做懒加载时,需要用Suspense包裹一下占位元素Outlet
在这里插入图片描述

Redux状态管理

下载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类型

json接口数据转ts类型在线链接

useState的ts使用

定义好对应的数组类型

interface bannertype {
    
    
  name: string
  age: number
}

使用的时候通过泛型传入,表明banner的类型是bannertype[]

const [banner, setbanner] = useState<bannertype[]>([])

类组件ts的使用

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

ts第三方库引入报错

我们install后引入报错
在这里插入图片描述
原因:没有进行类型声明
解决:
npm i --save-dev @types/styled-components

类名全局使用

如果某个类或者一些精灵图类名我们使用的很多,我们可以不用再styled-components里面写了,直接将对应的类写到全局引入的common.css里面,然后在对应jsx元素上面加上类名就行。
common.css文件
在这里插入图片描述

useState在点击页面的刷新按钮后状态会初始化,相当于重新加载useState的值,但是我们使用setState触发的组件更新会保存State的值

需求:点击tab菜单后刷新页面,tab菜单栏点击前状态保持

使用NavLink组件即可

使用antd轮播图组件

首先想使用组件带有的api函数,要绑定ref,这时候类型要怎么给呢?
引入类型和Carousel 组件

import type {
    
     FC, ReactNode, ElementRef } from 'react'
import {
    
     Carousel } from 'antd'

传入泛型,ElementRef又接收一个泛型

  const bannerRef = useRef<ElementRef<typeof Carousel>>(null)

使用api后报错
在这里插入图片描述
解决

  function handlePrevClick() {
    
    
    bannerRef.current?.prev()
  }
  function handleNextClick() {
    
    
    bannerRef.current?.next()
  }

style的>写法

比如content选择器,代表这个RecommendWrapper的直接子元素,不会干扰嵌套中的.content类选择器的样式
在这里插入图片描述

使用css实现最后一个竖杠不显示

在这里插入图片描述
解决
在这里插入图片描述
在item选择器
在这里插入图片描述

数据处理

在一个轮播图中展示五条数据,点击切换按钮切换到下五条
使用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属性,但是eslint检查报错

在这里插入图片描述
解决
使用ts的非空断言操作符

audioRef.current!.src = getSongPlayUrl(currentSong.id)

猜你喜欢

转载自blog.csdn.net/wyh666csdn/article/details/128676975