使用vite脚手架构建项目
npm create vite@latest
创建时,输入项目名称,并选择react和typescript
进入到文件夹,安装依赖,运行
如果npm安装没反应,可以使用代理方式
npm config set registry https://registry.npm.taobao.org
安装第三方插件和依赖
路由 react-router:npm install react-router-dom@6
状态管理react-redux:npm install @reduxjs/toolkit react-redux
UI组件库 antd:npm install antd
MOCK数据 mockjs:npm install mockjs
loading加载nprogress: npm install nprogress
request请求axios:npm install axios
less:npm install -D less
Mock vite:npm install -D vite-plugin-mock
nprogress 加载中类型:npm i -D @types/nprogress
依赖安装完后,package.json是这个样子
{
"name": "my-antd-vite-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^1.9.0",
"antd": "^4.24.2",
"axios": "^1.1.3",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.3"
},
"devDependencies": {
"@types/nprogress": "^0.2.0",
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"@vitejs/plugin-react": "^2.2.0",
"less": "^4.1.3",
"typescript": "^4.6.4",
"vite": "^3.2.0",
"vite-plugin-mock": "^2.9.6"
}
}
搭建完成后,页面效果(含主页、setup、错误三个页面,都可切换日夜间模式)
配置文件修改(vite.config.ts文件修改)
import react from "@vitejs/plugin-react";
import path from "path";
import { defineConfig } from "vite";
import { viteMockServe } from "vite-plugin-mock";
export default defineConfig({
plugins: [
react(),
//mock数据
viteMockServe({
mockPath: "mock",
localEnabled: true,
prodEnabled: false,
supportTs: false,
watchFiles: true,
injectCode: `
import { setupProdMockServer } from './mockProdServer';
setupProdMockServer();
`,
}),
],
resolve: {
//别名
alias: {
"~": path.resolve(__dirname, "./"), // 根路径
"@": path.resolve(__dirname, "src"), // src 路径
},
},
css: {
preprocessorOptions: {
//less文件
less: {
additionalData: `@import "${path.resolve(
__dirname,
"src/styles/variable.less"
)}";`,
// 支持内联 JavaScript
javascriptEnabled: true,
},
},
},
server: {
port: 3001,
host: "0.0.0.0",
open: false,
// proxy: {
// "/api": {
// target: "https://xxx.com/api/v1",
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/dev-api/, ""),
// },
// },
},
});
配置tsconfig.json
//在compilerOptions对象中添加属性
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
配置router 支持按需加载
路径:src/routers/index.tsx
import { lazy, Suspense, ReactNode } from "react";
import { createBrowserRouter, RouteObject } from "react-router-dom";
import LoadingComponent from "@/compontents/Loading";
import LayoutPage from "@/layout";
const Introduction = lazy(() => import("@/views/introduction"));
const Setup = lazy(() => import("@/views/setup"));
const Error404 = lazy(() => import("@/views/error/404"));
const load = (children: ReactNode): ReactNode => (
<Suspense fallback={<LoadingComponent />}>{children}</Suspense>
);
//定义路由数据
export const routes: RouteObject[] = [
{
path: "/",
element: <LayoutPage />,
children: [
{
index: true,
element: load(<Introduction />),
},
{
path: "setup",
element: load(<Setup />),
},
{
path: "*",
element: load(<Error404 />),
},
],
},
];
export default createBrowserRouter(routes);
配置store
//文件路径:src/store/index.ts
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import indexSlice from "./reducers/indexSlice";
//定义store
const store = configureStore({
reducer: {
index: indexSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
export default store;
//文件路径:src/store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./index";
// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
//文件路径:src/store/reducers/indexSlice.ts
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import type { RootState } from "../index";
import { getCatalog } from "@/api/category";
// 为 slice state 定义一个类型
interface IndexState {
catalogList?: any[];
status?: "idle" | "loading" | "failed";
}
// 使用该类型定义初始 state
const initialState: IndexState = {
catalogList: [],
};
//获取首页左侧文档目录
export const getCatalogList = createAsyncThunk("index/getCatalog", async () => {
const { data } = await getCatalog();
return data;
});
export const indexSlice = createSlice({
name: "index",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getCatalogList.fulfilled, (state, action) => {
const { CatalogList } = action.payload;
state.catalogList = CatalogList;
});
},
});
export const selectCatalogList = (state: RootState) => state.index.catalogList;
export default indexSlice.reducer;
修改入口文件main.tsx和App.tsx
//路径:src/main.tsx
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import store from "@/store/index";
import { Provider } from "react-redux";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<Provider store={store}>
<App />
</Provider>
);
//路径:src/App.tsx
import { FC, useState } from "react";
import { RouterProvider } from "react-router-dom";
import { GlobalContext } from "@/context";
import router from "./routers/index";
import "./App.less";
interface GlobalContext {
theme: string;
setTheme: (theme?: string) => void;
}
const App: FC = () => {
const [theme, setTheme] = useState<string>("light"); //主题 默认日间模式
const contextVal = { theme, setTheme } as GlobalContext;
return (
<div className="app-container">
<GlobalContext.Provider value={contextVal}>
<RouterProvider router={router} />
</GlobalContext.Provider>
</div>
);
};
export default App;
定义Context用来切换主题(日夜模式)
//文件路径:src/context.ts
import { createContext } from "react";
interface GlobalContext {
theme: string;
setTheme: (theme?: string) => void;
}
export const GlobalContext = createContext<GlobalContext>({
theme: "light",
setTheme: () => {},
});
定义layout
//文件路径src/layout/index.tsx
import React from "react";
import { Outlet } from "react-router-dom";
import { Layout } from "antd";
import { HeaderComponent, AsideComponent, FooterComponent } from "./components";
import "./style.less";
const { Header, Content, Footer } = Layout;
function DefaultLayout() {
return (
<Layout className="default-container text-gray-700 bg-white dark:bg-gray-900 dark:text-gray-300">
<Header
style={{
position: "sticky",
top: 0,
zIndex: 1,
width: "100%",
padding: 0,
backgroundColor: "transparent",
}}
>
<HeaderComponent />
</Header>
<Content className="main-content">
<div className="content-box">
<AsideComponent />
<div className="article-content" style={{ width: "79.17%" }}>
<Outlet />
</div>
</div>
</Content>
<Footer style={{ backgroundColor: "transparent", padding: 0 }}>
<FooterComponent />
</Footer>
</Layout>
);
}
export default React.memo(DefaultLayout);