文章目录
- 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.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
**同时需要安装插件 **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?: ReactNode
name?: string
age?: 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.dispatch
export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
export const useAppDisPatch: () => DispatchType = useDispatch
export 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/token
return 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 => IHomeData
request<T = any>(config: HYRequestConfig<T>) {
// 单次请求的成功拦截处理
if (config.interceptors?.requestSuccessFn) {
config = config.interceptors.requestSuccessFn(config)
}
// 返回Promise
return 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) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: T) => T
responseFailureFn?: (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) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: T) => T
responseFailureFn?: (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: string
targetId: number
targetType: number
titleColor: string
typeTitle: string
url: string
exclusive: boolean
scm: string
bannerBizType: 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: string
age?: number
}
interface IState {
message: string
counter: 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: number
message: string
address: string
height: number
direction: '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