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;它由一系列…

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文件夹里…

解线性方程组(一)——克拉默法则求解(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…

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;而是…

【图像分割 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…

计算机设计大赛 深度学习YOLO安检管制物品识别与检测 - python opencv

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov55 模型训练6 实现效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLO安检管制误判识别与检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&…

17.3.1.5 二值化(黑白)

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 二值化的算法主要有以下两种&#xff1a; 1、彩色图像灰度化后&#xff0c;根据灰度和阈值来确定颜色是黑色还是白色。通常情况下使…

STM32

Unix时间戳 BKP 读写备份寄存器 main.c #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Key.h"uint8_t KeyNum;uint16_t ArrayWrite[] {0x1234,0x2134}; uint16_t Arr…

Nvidia 携手 RTX 推出的本地运行 AI 聊天机器人

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

2024.2.16日总结(小程序开发8)

数据监听器 监听对象属性的变化 数据监听器支持监听对象中单个或多个属性的变化 纯数据字段 什么是纯数据字段 纯数据字段指的是哪些不用于页面渲染的data字段 应用场景:例如有些情况下&#xff0c;某些 data 中的字段既不会展示在界面上&#xff0c;也不会传递给其他组件…

html从零开始9:javaScript简介,语句、标识符,变量,JavaScript引入到文件【搬代码】

javaScript简介 javaScript语句、标识符 变量 var num 10; var就是固定声明,num就是变量名&#xff0c;10就是变量&#xff1b;<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Comp…

Flutter 动画(显式动画、隐式动画、Hero动画、页面转场动画、交错动画)

前言 当前案例 Flutter SDK版本&#xff1a;3.13.2 显式动画 Tween({this.begin,this.end}) 两个构造参数&#xff0c;分别是 开始值 和 结束值&#xff0c;根据这两个值&#xff0c;提供了控制动画的方法&#xff0c;以下是常用的&#xff1b; controller.forward() : 向前…

Docker笔记-搭建Python环境、安装依赖、打包镜像、导入镜像、编写bash脚本灵活调用

说明 适合无联网的机器及多Python的机器进行部署。 制作docker版Python环境 有网络及有docker的&#xff0c;拉取指定版本的python如&#xff1a; docker pull python:3.7 安装好后进入容器&#xff1a; docker run -it <name> /bin/bash 使用pip安装各种依赖&…