当前位置: 首页 > news >正文

react+ts【项目实战一】配置项目/路由/redux

文章目录

  • 1、项目搭建
    • 1、创建项目
    • 1.2 配置项目
      • 1.2.1 更换icon
      • 1.2.2 更换项目名称
      • 1.2.1 配置项目别名
    • 1.3 代码规范
      • 1.3.1 集成editorconfig配置
      • 1.3.2 使用prettier工具
    • 1.4 项目结构
    • 1.5 对css进行重置
    • 1.6 注入router
    • 1.7 定义TS组件的规范
    • 1.8 创建代码片段
    • 1.9 二级路由和懒加载
    • 1.10 redux-reduxtk
    • 1.10 axios的封装
  • 1.11 类组件和TS的结合
    • 1.12 redux和ts的结合

1、项目搭建

1、创建项目

  • 1、该项目使用的是ts创建的 所以需要加上--template typescript
    • create-react-app kiki_ts_react_music --template typescript
  • 2、整理项目结构 删除一些自己用不到的文件
  • 在这里插入图片描述
    在这里插入图片描述

1.2 配置项目

1.2.1 更换icon

在这里插入图片描述

1.2.2 更换项目名称

在index.html文件里面

在这里插入图片描述

1.2.1 配置项目别名

  • 1、npm i -D @craco/craco
  • 2、在根文件创建 craco.config.ts
const path = require("path");
const CracoLessPlugin = require("craco-less");// path.resolve返回当前文件的绝对路径 拼接+dir
const resolve = (dir) => path.resolve(__dirname, dir);
module.exports = {plugins: [{ plugin: CracoLessPlugin }],webpack: {alias: {"@": resolve("src"),},},
};
  • 3、修改tsconfig.json
    在这里插入图片描述
    "baseUrl": ".","paths": {"@/*": ["src/*"]}
  • 4、修改 package.json
  "scripts": {"start": "craco start","build": "craco build","test": "craco test","eject": "react-scripts eject"},

1.3 代码规范

1.3.1 集成editorconfig配置

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

  • 1、在根目录下创建.editorconfig文件
# http://editorconfig.orgroot = 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

**同时需要安装插件 **EditorConfig for VS Code

在这里插入图片描述

1.3.2 使用prettier工具

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 1.安装prettier

    npm install prettier -D

  • 2、配置.prettierrc文件:在根目录下创建该文件
    在这里插入图片描述

{"useTabs": false,"tabWidth": 2,"printWidth": 80,"singleQuote": true,"trailingComma": "none","semi": false
}
  • 3、创建.prettierignore忽略文件 在根目录下
/dist/*
.local
.output.js
/node_modules/****/*.svg
**/*.sh/public/*
  • 4、在package.json中配置一个scripts:
    "prettier": "prettier --write ."

执行 npm run prettier就会将项目全部按照prettier的配置进行格式化

1.4 项目结构

在这里插入图片描述

1.5 对css进行重置

  • 1、下载normalize.css
    cnpm install normalize.css
    在index.tsx里面引入import 'normalize.css'

  • 2、使用less
    cnpm install craco-less

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

在这里插入图片描述

  • 3、配置自定义的css
    在这里插入图片描述
    最后都在index.jsx中引入
import 'normalize.css'
import '@/assets/css/index.less'

1.6 注入router

npm install react-router-dom

  • 在tsx中 使用到dom的页面都需要引入import React from 'react'

  • router/index.tsx

import React from 'react'
import type { RouteObject } from 'react-router-dom'
import Discover from '@/views/discover'
import Mime from '@/views/mime'const routes: RouteObject[] = [{ path: '/', element: <Mime /> },{ path: '/discover', element: <Discover /> }
]export default routes
  • index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { BrowserRouter } from 'react-router-dom'
import 'normalize.css'
import '@/assets/css/index.less'const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(<BrowserRouter><App /></BrowserRouter>
)
  • index.tsx
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'function App() {return (<div className="App"><header className="App-header"><h1>hahah</h1><Link to="/discover">发现音乐</Link><Suspense fallback="正在加载">{useRoutes(routes)}</Suspense></header></div>)
}export default App

1.7 定义TS组件的规范

import React, { memo } from 'react'
import type { ReactNode } from 'react'// 定义传进来的props类型
interface IProps {// 在之前的版本props默认会有children是插槽 在后来取消了得自己写children?: ReactNodename?: stringage?: number
}const Download: React.FC<IProps> = (props) => {return (<div>{props.children}<h1>{props.age}</h1><h1>{props.name}</h1></div>)
}export default memo(Download)
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
import Download from './views/download'function App() {return (<div className="App"><header className="App-header"><h1>hahah</h1><Link to="/discover">发现音乐</Link><Download name="kiki"><h1>我是downLoad的插槽</h1></Download><Suspense fallback="正在加载">{useRoutes(routes)}</Suspense></header></div>)
}export default App

1.8 创建代码片段

首选项=>设置代码片段=>react-ts
在这里插入图片描述

生成代码片段的网站
https://snippet-generator.app/?description=&tabtrigger=&snippet=&mode=vscode
在这里插入图片描述

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.9 二级路由和懒加载

  • discover页面
import React, { memo, Suspense } from 'react'
import type { FC, ReactNode } from 'react'
import { Outlet, Link } from 'react-router-dom'interface IProps {children?: ReactNode
}const Discover: FC<IProps> = () => {return (<div><div><Link to="/discover/recommend">推荐</Link><Link to="/discover/ranking">排行榜</Link><Link to="/discover/songs">歌单</Link><Link to="/discover/djradio">主播电台</Link><Link to="/discover/artist">歌手</Link><Link to="/discover/album">新碟上架</Link></div>{/* 二级路由也可以用suspense */}<Suspense fallback="正在加载"><Outlet /></Suspense></div>)
}export default memo(Discover)
  • App.jsx
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
import Download from './views/download'function App() {return (<div className="App"><div className="nav"><Link to="/discover">发现音乐</Link><Link to="/mine">我的音乐</Link><Link to="/focus">关注</Link><Link to="/download">下载客户端</Link></div><Suspense fallback="正在加载">{useRoutes(routes)}</Suspense><div className="main"></div></div>)
}export default App

在这里插入图片描述

1.10 redux-reduxtk

cnpm install @reduxjs/toolkit react-redux

  • index.tsx 提供Provide
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import 'normalize.css'
import '@/assets/css/index.less'
import store from './store'const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(<Provider store={store}><BrowserRouter><App /></BrowserRouter></Provider>
)
  • store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import { useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux'
import counterReducer from './modules/counter'const store = configureStore({reducer: {counter: counterReducer}
})// 获取函数的返回类型
type GetStateFnType = typeof store.getState
// 获取函数返回类型的类型
type IRootState = ReturnType<GetStateFnType>
type DispatchType = typeof store.dispatchexport const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
export const useAppDisPatch: () => DispatchType = useDispatchexport default store
  • store/count.ts
import { createSlice } from '@reduxjs/toolkit'const counterSlice = createSlice({name: 'counter',initialState: {count: 1,message: 'hello'},reducers: {changeMessageAction(state, { payload }) {state.message = payload}}
})export const { changeMessageAction } = counterSlice.actions
export default counterSlice.reducer
  • 使用的页面
import React, { memo, Suspense } from 'react'
import type { FC, ReactNode } from 'react'
import { Outlet, Link } from 'react-router-dom'
import { useAppDisPatch, useAppSelector } from '@/store'
import { shallowEqual } from 'react-redux'
import { changeMessageAction } from '@/store/modules/counter'interface IProps {children?: ReactNode
}const Discover: FC<IProps> = () => {const { count, message } = useAppSelector((state) => ({count: state.counter.count,message: state.counter.message}),shallowEqual)const dispatch = useAppDisPatch()const changeMessage = (message: string) => {dispatch(changeMessageAction(message))}return (<div><div>{count}=={message}<button onClick={() => changeMessage('修改message')}>修改message</button><Link to="/discover/recommend">推荐</Link><Link to="/discover/ranking">排行榜</Link><Link to="/discover/songs">歌单</Link><Link to="/discover/djradio">主播电台</Link><Link to="/discover/artist">歌手</Link><Link to="/discover/album">新碟上架</Link></div>{/* 二级路由也可以用suspense */}<Suspense fallback="正在加载"><Outlet /></Suspense></div>)
}export default memo(Discover)

1.10 axios的封装

在这里插入图片描述

  • request/index.ts
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { HYRequestConfig } from './type'// 拦截器: 蒙版Loading/token/修改配置/*** 两个难点:*  1.拦截器进行精细控制*    > 全局拦截器*    > 实例拦截器*    > 单次请求拦截器**  2.响应结果的类型处理(泛型)*/class HYRequest {instance: AxiosInstance// request实例 => axios的实例constructor(config: any) {this.instance = axios.create(config)// 每个instance实例都添加拦截器this.instance.interceptors.request.use((config) => {// loading/tokenreturn config},(err) => {return err})this.instance.interceptors.response.use((res) => {return res.data},(err) => {return err})// 针对特定的hyRequest实例添加拦截器this.instance.interceptors.request.use(config.interceptors?.requestSuccessFn,config.interceptors?.requestFailureFn)this.instance.interceptors.response.use(config.interceptors?.responseSuccessFn,config.interceptors?.responseFailureFn)}// 封装网络请求的方法// T => IHomeDatarequest<T = any>(config: HYRequestConfig<T>) {// 单次请求的成功拦截处理if (config.interceptors?.requestSuccessFn) {config = config.interceptors.requestSuccessFn(config)}// 返回Promisereturn new Promise<T>((resolve, reject) => {this.instance.request<any, T>(config).then((res) => {// 单词响应的成功拦截处理if (config.interceptors?.responseSuccessFn) {res = config.interceptors.responseSuccessFn(res)}resolve(res)}).catch((err) => {reject(err)})})}get<T = any>(config: HYRequestConfig<T>) {return this.request({ ...config, method: 'GET' })}post<T = any>(config: HYRequestConfig<T>) {return this.request({ ...config, method: 'POST' })}delete<T = any>(config: HYRequestConfig<T>) {return this.request({ ...config, method: 'DELETE' })}patch<T = any>(config: HYRequestConfig<T>) {return this.request({ ...config, method: 'PATCH' })}
}export default HYRequest
  • request/type.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfigrequestFailureFn?: (err: any) => anyresponseSuccessFn?: (res: T) => TresponseFailureFn?: (err: any) => any
}export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {interceptors?: HYInterceptors<T>
}
  • config/index.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfigrequestFailureFn?: (err: any) => anyresponseSuccessFn?: (res: T) => TresponseFailureFn?: (err: any) => any
}export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {interceptors?: HYInterceptors<T>
}

环境变量也可以通过配置文件 但是前面需要加上REACT_APP_…

在这里插入图片描述

  • service/index.ts
import { BASE_URL, TIME_OUT } from './config'
import HYRequest from './request'const hyRequest = new HYRequest({baseURL: BASE_URL,timeout: TIME_OUT,interceptors: {requestSuccessFn: (config: any) => {return config}}
})export default hyRequest

在这里插入图片描述

  • 使用的页面
import React, { memo, useEffect, useState } from 'react'
import type { FC, ReactNode } from 'react'
import hyRequest from '@/service'interface IProps {children?: ReactNode
}export interface IBannerData {imageUrl: stringtargetId: numbertargetType: numbertitleColor: stringtypeTitle: stringurl: stringexclusive: booleanscm: stringbannerBizType: string
}const Recommend: FC<IProps> = () => {const [banners, setBanners] = useState<IBannerData[]>([])// 测试网络请求useEffect(() => {hyRequest.get({url: '/banner'}).then((res) => {setBanners(res.banners)})}, [])return (<div>{banners.map((item, index) => {return <div key={index}>{item.imageUrl}</div>})}</div>)
}export default memo(Recommend)
  • 可以在这个页面自动生成类型定义
    https://transform.tools/json-to-typescript

1.11 类组件和TS的结合

import React, { PureComponent } from 'react'
/*** state:* props:*/interface IProps {name: stringage?: number
}interface IState {message: stringcounter: number
}class Demo02 extends PureComponent<IProps, IState> {name = 'aaaa'state = {message: 'Hello World',counter: 99}// getSnapshotBeforeUpdate() {//   return { address: '庐山' }// }// componentDidUpdate(//   prevProps: Readonly<IProps>,//   prevState: Readonly<IState>,//   snapshot?: ISnapshot | undefined// ): void {}// constructor(props: IProps) {//   super(props)//   // this.state = {//   //   message: 'Hello World',//   //   counter: 100//   // }// }render(): React.ReactNode {return (<div>name: {this.props.name}age: {this.props.age}message: {this.state.message}counter: {this.state.counter}</div>)}
}export default Demo02

1.12 redux和ts的结合

import { createSlice, PayloadAction } from '@reduxjs/toolkit'interface IState {count: numbermessage: stringaddress: stringheight: numberdirection: 'left' | 'right' | 'up' | 'down'names: string[]
}const initialState: IState = {count: 100,message: 'Hello Redux',address: '广州市',height: 1.88,direction: 'left',names: []
}const counterSlice = createSlice({name: 'counter',initialState,reducers: {changeMessageAction(state, { payload }: PayloadAction<string>) {state.message = payload}}
})export const { changeMessageAction } = counterSlice.actions
export default counterSlice.reducer
http://www.lryc.cn/news/300891.html

相关文章:

  • 英文论文(sci)解读复现【NO.20】TPH-YOLOv5++:增强捕获无人机的目标检测跨层不对称变压器的场景
  • 第十五章 以编程方式使用 SQL 网关 - %SQLGatewayConnection 方法和属性
  • 【QTableView】
  • VS-Code-C#配置
  • 第七篇【传奇开心果系列】Python微项目技术点案例示例:数据可视化界面图形化经典案例
  • LeetCode 第33天 | 1005. K 次取反后最大化的数组和 135. 分发糖果 134. 加油站
  • PointMixer论文阅读笔记
  • [word] word分割线在哪里设置 #其他#经验分享
  • C++ 音视频原理
  • C# 只允许开启一个exe程序
  • 【Java程序员面试专栏 分布式中间件】Redis 核心面试指引
  • 2024年【高处安装、维护、拆除】模拟考试题库及高处安装、维护、拆除实操考试视频
  • 【QT+QGIS跨平台编译】之三十七:【Shapelib+Qt跨平台编译】(一套代码、一套框架,跨平台编译)
  • 【机器学习基础】决策树(Decision Tree)
  • 图神经网络DGL框架,graph classification,多个且不同维度的node feature 训练
  • 蓝桥杯(Web大学组)2022国赛真题:用什么来做计算 A
  • Linux POSIX信号量 线程池
  • Sentinel(理论版)
  • python3 获取某个文件夹所有的pdf文件表格提取表格并一起合并到excel文件
  • 【AIGC】Stable Diffusion的模型入门
  • 【JavaEE】_HTTP请求首行详情
  • Linux第48步_编译正点原子的出厂Linux内核源码
  • 程序员为什么不喜欢关电脑?
  • 【初始RabbitMQ】了解和安装RabbitMQ
  • Linux第56步_根文件系统第3步_将busybox构建的根文件系统烧录到EMMC
  • Linux进程间通信(三)-----System V消息队列
  • Elasticsearch:混合搜索是 GenAI 应用的未来
  • 态、势、感、知的偏序、全序与无序
  • 【从Python基础到深度学习】 8. VIM两种状态
  • java微服务面试篇