React18+Ts的网易云音乐项目搭建

877 阅读7分钟

React18+TS

一、项目搭建

1.1.创建项目

  • 由于用create-react-app脚手架创建react项目时不会询问是否使用ts,所以需要为其添加一个后缀

    create-react-app react_ts_music --template typescript
    

1.2.配置项目别名

  • 例如:src -> @

  • 但是由于脚手架创建出来的项目其已经对webpack的配置进行了封装,所以无法直接进行修改

    • 方式一:运行代码npm run eject 使webpack的配置暴露出来,从而修改其配置(不推荐)

    • 方式二:使用craco:create-react-app.config(帮助我们对webpack配置进行更改)

      • 安装craco

        由于使用的react-scripts是5以上的版本所以直接安装不行,需要安装alpha版本

        npm install @craco/craco@alpha -D
        
      • 使用craco

        1、创建craco.config.js文件

        const path = require('path')
        
        const resolve = (dir) => path.resolve(__dirname, dir)
        module.exports = {
          webpack: {
            alias: {
              '@': resolve('src')
            }
          }
        }
        
  • 除此之外还需要配置一下tsconfig.json文件,要不然TS检测会出现问题

    "baseUrl": ".",
    "paths": {
        "@/*": [
            "src/*"
        ]
    }
    
  • 最后需要carco去启动项目

    在package.json里更改scripts

    "scripts": {
        "start": "craco start",
        "build": "craco build",
        "test": "craco test",
        "eject": "react-scripts eject"
    },
    

1.3.代码规范

Ⅰ、集成editorconfig配置
  • Editorconfig有助于为不同IDE编辑器上处理同一项目的多个开发人员维护一致的编码风格。

  • //.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
    
  • VSCode需要安装一个插件:Editorconfig for VS Code

Ⅱ、使用prettier工具
  • Prettier是一款强大的代码格式化工具,支持js,ts,css,scss,jsx,angular,vue,graphql, json,md等语言,基本上前端能用到的文件格式它都能搞定,是当前最流行的代码格式化工具。

  • 1.安装prettier

    npm install prettier -D
    

    2.配置.prettier文件

    • useTabs: 使用tab缩进还是空格缩进,选择false;

    • tabWidth:tab是空格的情况下是几个空格,选择2个;

    • printWidth:当前字符的长度,推荐80,也可以100或者120;

    • singleQuote:使用单引号还是双引号,选择true,使用单引号;

    • traillingComma:在多行输入的尾逗号是否添加,设置为none,比如对象类型的最后一个属性是否添加一个逗号;

    • semi:语句末尾是否要加分号,默认值true,选择false表示不加;

    • //.prettierrc
      {
        "useTabs": false,
        "tabWidth": 2,
        "printWidth": 100,
        "singleQuote": true,
        "trailingComma": "none",
        "bracketSpacing": true,
        "semi": false
      }
      
  • 创建.prettierignore忽略文件

/build/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*

安装VS Code插件Prettier - Code formatter,并且在设置中搜索Editor: Default Formatter选择Prettier,format on save=>勾选

配置脚本:

//package.json
"prettier": "prettier --write ."
npm run prettier

作用:用prettier对所有文件进行格式化

Ⅲ、使用ESlint

1.安装ESlint的插件

2.解决ESlint和prettier冲突的问题

安装插件:

npm install eslint -D

//由于配置过多需要默认去配置
npx eslint --init

由于之前在配置项目别名时使用到了require所以要忽略这个报错

rules: {
    '@typescript-eslint/no-var-requires': 'off'
  }

3.在设置中找到setting.json文件配置一些eslint(目前vscode不必要)

"eslint.alwaysShowStatus": true,
"eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typrscriptreact"
]

4.保持prettier和eslint代码风格保持一致

npm install eslint-plugin-prettier -D  eslint -config-prettier -D
//.eslintrc.js  
extends: [
    'plugin:prettier/recommended'
  ]

1.4.样式重置

1.引入normalize.css文件

npm install normalize.css

2.在index.tsx中进行导入

import 'normalize.css'

3.自己手写一些重置样式

1.5.less配置

npm install craco-less@2.1.0-alpha.0

配置:

//carco.config.js
const path = require('path')
const CracoLessPlugin = require('craco-less')

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

module.exports = {
  plugins: [{ plugin: CracoLessPlugin }],
  webpack: {
    alias: {
      '@': resolve('src')
    }
  }
}

1.6.路由的配置

  • 在单独的router文件夹中配置自己的路由并导出

    const routes: RouteObject[] = []
    
  • 在App中使用routes

    <div className="main">{useRoutes(routes)}</div>
    
  • 千万不可忘记用Router包裹App组件

    root.render(
        <HashRouter>
          <App />
        </HashRouter>
    )
    
  • 性能优化:路由懒加载

    const Discover = lazy(() => import('@/views/discover'))
    
  • 当点击切换页面时会发现报错,原因是懒加载进行了分包处理,当时组件还未下载下来,所以出现了报错

​ 解决办法:包裹suspense,在下载过程中展示

<Suspense fallback="">
    <div className="main">{useRoutes(routes)}</div>
</Suspense>
  • 配置二级路由

    children:[
        ....
    ]
    
  • 因为为二级路由需要一个占位,同样需要包裹一个suspense,防止二级路由出现闪烁

    <Suspense fallback="">
        <Outlet />
    </Suspense>
    

1.7.生成模板

生成对应函数式组件的代码模板

import React, { memo } from 'react'
import type { FC, ReactNode } from 'react'

interface IProps {
  children?: ReactNode
}

const Template: FC<IProps> = () => {
  return <div>Template</div>
}

export default memo(Template)

1.8.状态管理

  • 安装redux

    npm install @reduxjs/toolkit react-redux
    
  • 创建store

    import { configureStore } from '@reduxjs/toolkit'
    import counterReducer from './modules/counter'
    
    const store = configureStore({
      reducer: {
        counter: counterReducer
      }
    })
    
    export default store
    
    //index.tsx
    root.render(
      <Provider store={store}>
        <HashRouter>
          <App />
        </HashRouter>
      </Provider>
    )
    
  • 创建store片段

    import { createSlice } from '@reduxjs/toolkit'
    
    const counterSlice = createSlice({
      name: 'counter',
      initialState: {
      },
      reducers: {
        changeMessageAction(state, { payload }) {
          state.message = payload
        }
      }
    })
    
    export const { changeMessageAction } = counterSlice.actions
    export default counterSlice.reducer
    
  • 定义state的类型

    自己封装一个useAppSelector

    type GetStateFnType = typeof store.getState
    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
    

    TypeScript 快速开始 | Redux 中文官网

1.9.axios二次封装

  • 复用之前的vue3+ts的封装

1.10.切换开发环境和生产环境

由于在开发阶段需要测试一些数据,而这些数据不应该在生产环境下暴露出去

  • 手动切换:不可取

  • 依赖当前环境development/production

    这个环境变量由node注入,可在react-app-env.d.ts中查看

    let BASE_URL = ''
     if (process.env.NODE_ENV === 'development') {
       BASE_URL = 'http://codercba.dev:9002'
     } else {
       BASE_URL = 'http://codercba.prod:9002'
     }
    
  • 从定义的环境变量的配置文件中, 加载变量

    //.env.development
    REACT_APP_BASE_URL=http://codercba.dev:9002
    
    //.env.production
    REACT_APP_BASE_URL=http://codercba.prod:9002
    
    //注意:在给变量命名时一定要以REACT_APP开头
    

1.11.类组件的约束

由于类组件有自己的状态需要管理,所以需要约束类组件的state和props,以便有更好的代码提示

  • PureComponent接受两个参数,第一个为props的约束类型,第二个为state的约束类型,第三个为getSnapshotBeforeUpdate函数的返回值类型(基本不用)
interface IProps {
  name: string
  age?: number
}
interface IState {
  message: string
  counter: number
}
class Demo02 extends PureComponent<IProps, IState> {
  name = 'aaaa'
  state = {
    message: 'Hello World',
    counter: 99
}
render(){}
}

由于组件会自动调用super方法,并且可以直接定义数据,所以可以省略掉constructor函数,简化代码

1.12.使用styled-components

  • 安装styled-conponents

    npm install styled-component -D
    
  • 引入styled-components时,出现模块未声明的报错

    安装类型声明

    npm install --save-dev @types/styled-components
    

二、注意事项

2.1.给link组件设置选中样式

这个时候可以使用NavLink,其内部会自动去比较hash值看是否匹配路径,

由此来动态添加active,并且刷新的时候也不会回到首页

当然如果不想添加active,想添加自己的类可以写如下代码

<NavLink
    to={item.link}
    className={({ isActive }) => {
        return isActive ? 'active' : undefined
    }}
    >
    {item.title}
    <i className="icon sprite_01"></i>
</NavLink>

可以参考官方文档:NavLink v6.10.0 | React Router

2.2.图片资源

  • 网易云的图片资源支持高斯模糊和缩放
    • 高斯模糊:url+?imageView&blur= 参数x参数
    • 缩放:url+?param= width x height

2.3.ref绑定组件

  • 当我们去使用ref绑定组件的时候,由于未声明其类型,所以无法获取其的current属性

    解决:react为我们提供了ElementRef

    import type { ElementRef } from 'react'
    
    const bannerRef = useRef<ElementRef<typeof 组件名>>(null)
    

2.4.图片的获取

  • 如果直接去获取原图片,由于图片过大消耗性能,所以可以封装一个getImageSize的工具函数

    export function getImageSize(
      imageUrl: string,
      width: number,
      height: number = width
    ) {
      return imageUrl + `?param=${width}x${height}`
    }
    

这样在获取图片时就能获取一张缩放了的图片,优化了性能

2.5.同时发送多个网络请求,并保持返回顺序

  • 在开发中有些数据放在一起会比较好处理,但是它们会有一定顺序,而服务器返回的数据顺序与发送的顺序无关,所以就要自定义一个promis数组来接收,并且调用它的all方法,既保证了都拿到了数据,也保证了顺序

    const promises: Promise<any>[] = []
    for (const id of rankingIds) {
        promises.push(getPlaylistDetail(id))
    }
    
    Promise.all(promises).then((res) => {
        const playlists = res.map((item) => item.playlist)
        console.log(playlists)
        dispatch(changeRankingsAction(playlists))
    })
    

    Promise后面接收的泛型是用来约束resolve(‘.....’),reject(‘....’)中的参数的

三、后续补充