框架原理精校教程
React + TypeScript 工程实践
从项目创建、路由和状态管理,到 API 与组件类型,串起一套可维护的工程骨架。
创建项目
- create-react-app 创建项目 默认webpack配置
# 创建项目
create-react-app my-app --template typescript
- vite创建项目
pnpm create vite my-app --template react-ts
项目配置
- Tsconfig 使用默认配置
编辑器给予更好的代码提示,需要在默认配置上添加paths的配置
{
"compilerOptions": {
// ...
"paths": {
"@/*": [
"src/*"
],
//...
},
},
// ...
}
- prettier
代码格式化工具,统一代码格式
- eslint
代码风格校验,统一风格
- editorconfig
统一不同的编辑器的设置,如缩进等
- husky
Git hook,代码提交前的会自动触发的钩子,可以用于prettier和eslint的格式化校验
- craco 配置webpack
使用craco配置webpack,注意craco配置的层级问题,有外层和选项plugin和webpack选项下的plugin,配置方式略有差异
路由
- 安装react-router-dom
npm install react-router-dom
# 当前版本为v6
- routes.tsx 路由的配置文件
//routes.tsx
import React, { lazy } from "react";
import { Navigate } from 'react-router-dom'
import type { RouteObject } from "react-router-dom";
//懒加载
const Home =lazy(()=>import("@/views/home")) ;
const routes: RouteObject[] = [
{
path: "/",
element: <Navigate to="/home"/>,
},
{
path: "/home",
element: <Home />,
},
];
export default routes;
- index.tsx 顶层使用hashRouter组件或者Router组件
import React from 'react'
import { createRoot } from 'react-dom/client'
import { HashRouter } from 'react-router-dom'
import App from './App'
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(
<HashRouter>
<App />
</HashRouter>
);
- app.tsx app组件中,应用配置的路由,使用useRoutes
import React, { Suspense } from 'react'
import type { FC ,ReactNode} from 'react'
import { useRoutes } from 'react-router-dom'
import Footer from "@/views/footer";
import Header from '@/views/header';
import routes from '@/routers'
interface IProps {
children?: ReactNode
}
const App: FC<IProps> = (props) => {
return (
<div>
<Header></Header>
<Suspense fallback="">
// 使用路由懒加载,则需要使用 Suspense组件
<div>
{useRoutes(routes)}
</div>
</Suspense>
<Footer></Footer>
</div>
)
}
export default App
redux 配置
- 安装 @reducjs/toolkit & react-redux(redux会在安装react-redux的时候一起安装)
npm install react-redux @reduxjs/toolkit
#@reducjs/toolkit & react-redux
- 创建store
import configureStore from '@reduxjs/toolkit';
import homeReducer from './modules/home'
import {
TypedUseSelectorHook,
useSelector,
useDispatch,
shallowEqual,
} from "react-redux";
const store = sonfigureStore({
reducer:{
home:homeReducer ,
}
})
//为useSelector注入类型,让后面使用时返回的数据带上类型
type RootState = ReturnType<typeof store.getState>;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = useDispatch;
export const appShallowEqual = shallowEqual;
export default store
- 创建reducer
//homeReducer.js
import {createSlice,createAsyncThunk} from '@reduxjs/toolkit'
//异步请求
export const homeAsyncAction = createAsyncThunk('homeAsync',async (params,{dispatch})=>{
//发起异步请求
//获取结果后可以返回结果,也可以直接dispatch某个action
})
const homeReducer = createSlice({
name:'home',
initialState:{
counter:0
},
reducers:{
changeCounter(state,{payload}){
state.counter = payload
}
},
extraReducers: {},//可选
})
export const {changeCounter } = homeReducer.actions
export default homeReducer.reducer
- 注入全局
index.tsx //注入全局
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(
//注入到全局
<Provider store={store}>
<App />
</Provider>
);
- 组件使用
import { useAppSelector, useAppDispatch, appShallowEqual } from '@/store'
import {changeCounter} from '@/store/modules/home'
import React, { memo } from 'react'
import type { FC ,ReactNode} from 'react'
interface IProps {
children?: ReactNode
}
const Home: FC<IProps> = (props) => {
const { counter} = useAppSelector(state => ({
counter:state.home.counter
}),appShallowEqual)
const dispatch = useAppDispatch()
return (
<div>Home
<button onClick={e=>dispatch(changeCounter(counter+1) )}>+1</button>
<div>{ counter}</div>
</div>
)
}
export default memo(Home)
- 异步处理
API封装
对axios进行二次封装,实现定制化
- 自定义全局拦截器、实例拦截器、单次请求拦截器,针对不同的需求可以实现不同的效果,也可以快速扩展
import axios from "axios";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
// 自己扩展的拦截器
interface generalIntercepters<T = AxiosResponse> {
requestSuccFn?: (config: AxiosRequestConfig) => AxiosRequestConfig;
requestErrFn?: (err: any) => any;
responseSuccFn?: (res: T) => T;
responseErrFn?: (err: any) => any;
}
// 自己扩展的请求配置
interface GeneralRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: generalIntercepters<T>;
}
class GeneralRequest {
instance: AxiosInstance;
constructor(config: GeneralRequestConfig) {
// 创建实例
this.instance = axios.create(config);
// 全局拦截器,可以做统一操作
this.instance.interceptors.request.use(
(config) => {
console.log("全局请求拦截器");
return config;
},
(err) => {}
);
//全局响应拦截
this.instance.interceptors.response.use(
(res) => {
return res.data;
},
(err) => err
);
// 实例拦截器
this.instance.interceptors.request.use(
config.interceptors?.requestSuccFn,
config.interceptors?.requestErrFn
);
this.instance.interceptors.response.use(
config.interceptors?.responseSuccFn,
config.interceptors?.responseErrFn
);
}
//请求
request<T = any>(config: GeneralRequestConfig<T>) {
// 单独的请求拦截
if (config.interceptors?.requestSuccFn) {
config = config.interceptors.requestSuccFn(config);
}
return new Promise<T>((resolve, reject) => {
this.instance
.request<any, T>(config)
.then((res) => {
// 单独的响应拦截
if (config.interceptors?.responseSuccFn) {
res = config.interceptors.responseSuccFn(res);
}
console.log("请求响应");
resolve(res);
})
.catch((err) => reject(err));
});
}
get<T = any>(config: GeneralRequestConfig<T>) {
return this.request<T>({ ...config, method: "GET" });
}
post<T = any>(config: GeneralRequestConfig<T>) {
return this.request<T>({ ...config, method: "POST" });
}
put<T = any>(config: GeneralRequestConfig<T>) {
return this.request<T>({ ...config, method: "PUT" });
}
delete<T = any>(config: GeneralRequestConfig<T>) {
return this.request<T>({ ...config, method: "DELETE" });
}
}
export default GeneralRequest;
组件使用ts
函数式组件
import React,{memo} from 'react'
import type {FC,ReactNode} from 'react'
//定义传入的props类型
interface IProps {
children?:ReactNode
name:string
}
//使用Fc泛型
const Home:FC<IProps> = (props)=>{
return <div></div>
}
export default memo(Home)
类组件
import React, { PureComponent } from 'react'
import type {ReactNode} from 'react'
//定义
interface IProps {
children?: ReactNode
}
//定义组件内的状态数据
interface IState{
name: string
}
export default class songs extends PureComponent<IProps, IState> {
state: IState = {
name: 'songs'
}
render() {
return (
<div>songs</div>
)
}
}
ref绑定时的类型
import type {ElementRef} from 'react'
import {useRef} from 'react'
const eleRef = useRef<ElementRef<typeof Swiper >>(null)