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.mzph.cn/news/685495.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c入门第十八篇——支持学生数的动态增长(链表,指针的典型应用)

数组最大的问题&#xff0c;就是不支持动态的扩缩容&#xff0c;它是静态内存分配的&#xff0c;一旦分配完成&#xff0c;其容量是固定的。为了支持学生的动态增长&#xff0c;这里可以引入链表。 链表 在C语言中&#xff0c;链表是一种常用的数据结构&#xff0c;它由一系列…

动态规划12-零钱兑换(Java)

12.零钱兑换 题目描述 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额&#xff0c;返回 -1 。 你可以认为每种硬…

STM32 寄存器操作 GPIO 与下降沿中断

一、如何使用stm32寄存器点灯&#xff1f; 1.1 寄存器映射表 寄存器本质就是一个开关&#xff0c;当我们把芯片寄存器配置指定的状态时即可使用芯片的硬件能力。 寄存器映射表则是开关的地址说明。对于我们希望点亮 GPIO_B 的一个灯来说&#xff0c;需要关注以下的两个寄存器…

Ps:创建调色观察图层组

人们往往受图像内容、所用显示器、自身对色彩敏感程度等的影响&#xff0c;无法准确地把握一张照片的明暗关系或色彩关系&#xff0c;因此导致修图时无方向、不精准。 如果通过数字化的方式建立观察图层&#xff08;组&#xff09;来辅助我们客观地分析照片&#xff0c;从而可以…

ARM编译器5.06下载安装

ARM编译器5.06下载安装 1.官网下载 进入官方网站ARM Complier v5.06官网下载页面 进入后的界面为 往下翻&#xff0c;找到如图位置的5.06 for windows的文件&#xff0c;点击下载&#xff0c;下载时需要登录账号 2.安装 先解压下载的压缩文件&#xff0c;在installer文件夹里…

react中render阶段做了什么

首先说明一个概念&#xff1a; render阶段对应的是Reconciler&#xff08;协调器&#xff09;&#xff0c; commit阶段对应的的是Renderer&#xff08;渲染器&#xff09; render阶段开始于performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的调用。这取决于本次更新是…

解线性方程组(一)——克拉默法则求解(C++)

克拉默法则 解线性方程组最基础的方法就是使用克拉默法则&#xff0c;需要注意的是&#xff0c;该方程组必须是线性方程组。 假设有方程组如下&#xff1a; { a 11 x 1 a 12 x 2 ⋯ a 1 n x n b 1 a 21 x 1 a 22 x 2 ⋯ a 2 n x n b 2 ⋯ ⋯ ⋯ a n 1 x 1 a n 2 x 2…

[前端开发] CSS基础知识 [下]

上篇:CSS 基础知识 [上] CSS基础知识 [下] CSS 新特性媒体查询雪碧图字体图标 CSS 新特性 圆角 (border-radius) 通过 border-radius 属性为元素添加圆角。类型: border-radius: a b c d: 四个值分别为左上|右上|右下|左下角border-radius: a b c : 三个值分别为左上|右上和左下…

re:从0开始的CSS之旅 18. z-index

1. z-index z-index 属性用于设置元素的叠放层次&#xff0c;属性值可以为一个整数&#xff0c;整数值越大越优先显示 注意&#xff1a;z-index只对开启了定位的元素有效 示例如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta…

洛谷C++简单题小练习day13—文字处理软件

day13--文字处理软件--2.16 习题概述 题目描述 你需要开发一款文字处理软件。最开始时输入一个字符串作为初始文档。可以认为文档开头是第 0 个字符。需要支持以下操作&#xff1a; 1 str&#xff1a;后接插入&#xff0c;在文档后面插入字符串 str&#xff0c;并输出文档的…

ubuntu20修改xorg.conf实现双屏幕输出

如果显卡工作正常是不需要自己手动编写xorg.conf的&#xff08;这个文件一般不存在或者是空的&#xff09;&#xff0c;系统会根据xorg.conf的缺省自动设置屏幕。 但有时候有的屏幕输出不对&#xff0c;想手动固定一下配置。比如我的ROG想设置内屏用intel驱动&#xff08;集显…

HTTP请求的构造方式

前言&#xff1a; 在详解完HTTP协议的请求和响应格式以后&#xff0c;对HTTP协议就会有更深层次的了解。接下来就要了解HTTP协议的具体的一些用法基础。 在HTTP协议中有两个核心的作用&#xff1a;&#xff08;1&#xff09;如何让客户端构造一个HTTP请求&#xff1b;&#xff…

macOS 安装 conda

macOS 安装 conda 安装 conda参考 Conda是一个开源的软件包管理系统和环境管理系统&#xff0c;用于安装和管理软件包和其依赖项。 安装 conda mkdir miniconda3 cd miniconda3 bash Miniconda3-latest-MacOSX-x86_64.sh$ conda list参考 macOS 安装 conda开始使用conda

OpenAI Sora是世界模型?

初见Sora&#xff0c;我被OpenAI的野心震撼了。 他们不仅想教会AI理解视频&#xff0c;还要让它模拟整个物理世界&#xff01;这简直是通用人工智能的一大飞跃。 但当我深入了解后&#xff0c;我发现Sora比我想象的更复杂、更强大。 Sora不是简单的创意工具&#xff0c;而是…

8086指令小结

所有指令 &#xff08; 1 &#xff09;立即数不能作为目的操作数。 &#xff08; 2 &#xff09;不能在 2 个存储单元之间直接进行操作&#xff08;串操作除外&#xff09; 。 &#xff08; 3 &#xff09; MOV 指令和堆栈指令是惟一能对段寄存器进行操作的指令。 &…

【图像分割 2023】BRAU-Net++

【图像分割 2023】BRAU-Net 论文题目&#xff1a;BRAU-Net: U-Shaped Hybrid CNN-Transformer Network for Medical Image Segmentation 中文题目&#xff1a; 论文链接&#xff1a;[2401.00722] BRAU-Net: U-Shaped Hybrid CNN-Transformer Network for Medical Image Segment…

人工智能专题:通过AI转变保险(英译中)

今天分享的是人工智能系列深度研究报告&#xff1a;《人工智能专题&#xff1a;通过AI转变保险&#xff08;英译中&#xff09;》。 &#xff08;报告出品方&#xff1a;VIEWPOINT&#xff09; 在新时代释放数据的力量 在数据和人工智能 &#xff08; AI &#xff09; 融合的…

Leetcode 42. 接雨水

题意理解&#xff1a; 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 左边的柱子和右边的柱子形成围栏&#xff0c;可以使中间能够积水 求最大的积水面积。h*w 解题思路&#xff1a; 1.横向求解 这里的单…

vscode写MATLAB配置

vscode写MATLAB python下载 官网说明Versions of Python Compatible with MATLAB Products by Release - MATLAB & Simulink 不确定这三列都表示什么意思&#xff0c;尽量安装这三列都有的python版本吧&#xff0c;我安装的 MATLAB R2023b,python选择的是3.11.5 …

政安晨:【示例演绎】【Python】【Numpy数据处理】快速入门(三)—— 数组的操作

准备 这是Numpy数据处理的示例演绎系列文章的第三篇&#xff0c;我的前两篇文章为&#xff1a; 政安晨&#xff1a;【示例演绎】【Python】【Numpy数据处理】快速入门&#xff08;一&#xff09;https://blog.csdn.net/snowdenkeke/article/details/136125773 政安晨&#x…