一 、vite命令构建(yarn搭建)
yarn create vite----根据提示填写
或
yarn create vite 项目名称 --template react-ts
二 、配置vite启动命令以及添加文件目录
1.vite.config.ts
export default defineConfig({
plugins: [react()],
// 服务器选项
server:{
host:'0.0.0.0',// 输入ip可以访问,指定服务器应该监听哪个 IP 地址。 如果将此设置为 0.0.0.0 或者 true 将监听所有地址,包括局域网和公网地址。
open:true,//开发服务器启动时,自动在浏览器中打开应用程序。
}
});
运行结果ip展示为:
➜ Local: http://localhost:5173/
➜ Network: http://2.0.0.1:5173/
➜ Network: http://192.168.xxx.xxx:5173/
2.文件目录
react项目基本src目录需要添加的有:路由router,状态管理库redux,接口请求api,公共组件Components,全局常量constants(按照项目而定),钩子hooks,布局layout,页面pages,工具utils,插件声明plugins.d.ts(按照项目而定)
同时在vite.config.ts和tsconfig.json配置@/xxx 目录
// 路径变量vite.config.ts
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
"compilerOptions":{
.......
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
3.安装antd router6 redux (提示:先把项目停止再安装)
yarn add antd
yarn add react-router-dom@6
yarn add react-redux @reduxjs/toolkit
安装less
yarn add less
vite.config.ts配置less
// css 预处理器
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
},
三、基本路由编写
1.新建home和login页面
pages/index.ts 统一暴露
import { lazy } from "react";
export const PageHome = lazy(() => import("@/pages/home"));
export const PageLogin = lazy(() => import("@/pages/login"));
2.实现路由跳转
router/index.tsx
const routeList: RouteObject[] = [
{
path: "/home",
element: <PageHome />,
},
{
path: "/login",
element: <PageLogin />,
},
];
const RouterRender = () => {
return useRoutes(routeList);
};
export default RouterRender;
App.tsx
function App() {
return (
<BrowserRouter>
<RouterRender />
</BrowserRouter>
);
}
3.基本路由编写
// 导航路由
const navRoutes = [
{
path: "/home",
element: lazyLoad(<PageHome />),
meta: {
title: "首页",
icon: "HomeOutlined",
},
},
];
// 全屏路由
const staticRoutes = [
{
path: "./login",
element: lazyLoad(<PageLogin />),
meta: {
title: "登录",
},
},
];
// 容错路由
const notFoundAndNoPower = [
{
path: "*",
element: <Page404 />,
meta: {
title: "notFound",
},
},
{
path: "*",
element: <Page403 />,
meta: {
title: "notPower",
},
},
];
const routeList: RouteObject[] = [
...staticRoutes,
...navRoutes,
...notFoundAndNoPower,
];
const RouterRender = () => {
return useRoutes(routeList);
};
// export const localRouters=getNavRouter(navRoutes)
export default RouterRender;
四、Layout布局
1.使用ant全局配置
App.tsx
import RouterRender from "@/router";
import { BrowserRouter } from "react-router-dom";
import { ConfigProvider } from "antd";
import zhCN from "antd/es/locale/zh_CN";
import "antd/dist/reset.css";
function App() {
return (
<ConfigProvider locale={zhCN}>
<BrowserRouter>
<RouterRender />
</BrowserRouter>
</ConfigProvider>
);
}
export default App;
2.编写layou基本页面以及路由
layout->index.tsx
(里面具体组件样式自己编写)
import { Layout } from "antd";
import styles from "./layout.module.less";
import React from "react";
import ComponentHeader from "./components/ComponentHeader";
import ComponentTabsView from "./components/TabsView";
import { Outlet } from "react-router";
import { LAYOUTS } from "@/constans";
const { Header, Footer, Sider, Content } = Layout;
const ComponentLayout: React.FC = () => {
return (
<Layout>
<ComponentHeader />
<Layout className={styles.container}>
<Sider className={styles.sider} width={206}></Sider>
<Content className={styles.content}>
<ComponentTabsView />
<Content className={styles.outlet}>
<Outlet />
</Content>
<Footer className={styles.footer}>{LAYOUTS.copyright}</Footer>
</Content>
</Layout>
</Layout>
);
};
export default React.memo(ComponentLayout);
layout->layout.module.less
.container {
height: calc(100vh - 48px);
background-color: red;
.sider {
background-color: #fff;
}
.outlet {
margin: 16px;
height: calc(100vh - 48px - 30px - 46px - 51px);
background-color: rebeccapurple;
}
.footer {
text-align: center;
font-size: 12px;
box-shadow: 0 0 5px 1px hsl(0deg 0% 67% / 20%);
}
}
路由修改
const routeList: RouteObject[] = [
{
path: "/",
element: <ComponentLayout />,
children: navRoutes,
},
...staticRoutes,
...notFoundAndNoPower,
];
展示效果
利用路由懒加载Suspense结合nprogress
yarn add nprogress(别忘了plugins.d.ts声明)
component–>Nprogress–>index.tsx
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { useEffect } from "react";
const ComponentNprogress: React.FC = () => {
NProgress.start();
useEffect(() => {
NProgress.done();
}, []);
return <div></div>;
};
export default ComponentNprogress;
router.tsx
之前统一导出的页面借用lazy内置方法,依赖内置组件Suspense,给lazy加上loading指示器组件的一个容器组件(Suspense目前只和lazy配合实现组件等待加载指示器的功能)
编写路由的时候使每个路由对象被lazyLoad包裹这样在layout里面切换路由的时候就实现路由懒加载(上面也会有进度条)
// 懒加载组件
const lazyLoad = (children: ReactNode) => (
<Suspense fallback={<ComponentNprogress />}>{children}</Suspense>
);
五、store-----@
reduxjs/toolkit
redux官网https://redux-toolkit.js.org/tutorials/typescript
基本目录,目前只能确定一个用户相关的切片先写文件
index.ts创建toolkit
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {},
});
export default store;
六、配置环境变量、模式
1.vite官网:https://cn.vitejs.dev/guide/env-and-mode.html#env-files
具体情况根据项目而定
由于本项目后端不处理跨域,所以前端开发的时候需要代理,为了区分两种环境不同,代理地址开发环境为/gateway/;生产环境和测试环境为/api(/为相对路径,所以请求的时候看到的是:服务器ip地址/api/接口地址,看不到真是的后端地址的)
.evn.devlopment
#开发环境
VITE_ENV='delevopment'
VITE_API_URL='/getway'
VITE_SERVER_NAME='http://1x.1x5.1x3.222:18082'
.env.production
#线上环境
VITE_ENV='production'
VITE_API_URL='/api'
VITE_SERVER_NAME='http://x0.1xx.1x3.x22:18082'
本地开发代理
vite.config.js
// 服务器选项
server: {
host: "0.0.0.0", // 输入ip可以访问,指定服务器应该监听哪个 IP 地址。 如果将此设置为 0.0.0.0 或者 true 将监听所有地址,包括局域网和公网地址。
open: true, //开发服务器启动时,自动在浏览器中打开应用程序。
proxy: {
"/getway": {
target: loadEnv(mode, process.cwd()).VITE_SERVER_NAME, //获取当前环境的变量
rewrite: (path) => path.replace(/^\/getway/, ""),//发送请求的时候把代理的地址删了
},
},
},
测试环境和线上环境都是用nginx代理
打包之后根据不同的环境配置nginx代理,代理地址就是/api了(具体配置自行百度哈)
2.模式
默认的通过运行不同命令走不同的环境不用单独配置了
七、请求api
1.请求需要token,存在cookie中,所以先写一个工具对浏览器缓存的处理
utils–>storage.ts
yarn add js-cookie
import Cookies from "js-cookie";
/**
*window.localStorage浏览器永久缓存
* @method set 设置永久缓存
@method get 设置永久缓存
@method remove 设置永久缓存
@method clear 设置永久缓存
*/
export const Local = {
// 设置永久缓存
set(key: string, val: any) {
// 防止token被误存再local中
if (key === "token") return Cookies.set(key, val);
window.localStorage.setItem(key, JSON.stringify(val));
},
//获取永久缓存
get(key: string) {
if (key === "token") return Cookies.get(key);
let json: any = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
if (key === "token") return Cookies.remove(key);
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
Cookies.remove("token");
window.localStorage.clear();
},
};
/**
*window.localStorage浏览器临时缓存
* @method set 设置临时缓存
@method get 设置临时缓存
@method remove 设置临时缓存
@method clear 设置临时缓存
*/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
let json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
window.sessionStorage.clear();
},
};
同时要声明一下,要不ts不认识js-cookie
plugins.ts
declare module "js-cookie";
2.写api请求
yarn add axios
api–>serve.ts
/*
* @Description:api请求
* @Author: zt
* @Date: 2023-03-13 15:48:57
* @LastEditors: zt
* @LastEditTime: 2023-03-17 11:40:10
*/
import axios from "axios";
import {
handleAuth,
handleRequestHeader,
handleNetworkError,
handleAuthError,
handleGeneralError,
} from "./tools";
// 请求拦截
axios.interceptors.request.use((config) => {
config = handleRequestHeader(config);
config = handleAuth(config);
return config;
});
// 响应拦截
axios.interceptors.response.use(
(response) => {
// 请求响应不成功直接返回错误
if (response.status !== 200) return Promise.reject(response.data);
// 请求通过进行拦截处理(登录失效、接口返回非200)
handleAuthError(response.data.code) &&
handleGeneralError(response.data.code, response.data.message);
return response;
},
(err) => {
// 请求未成功
handleNetworkError(err?.response?.status);
Promise.reject(err.response);
}
);
interface FcResponse<T> {
code: number;
message: string;
data: T;
}
interface IAnyObj {
[index: string]: unknown;
}
// 请求方式
export const Get = <T>(
url: string,
params: IAnyObj = {},
config?: any
): Promise<[any, FcResponse<T> | undefined]> =>
new Promise((resolve) => {
axios
.get(url, { params, ...config })
.then((result) => {
resolve([null, result.data as FcResponse<T>]);
})
.catch((err) => {
resolve([err, undefined]);
});
});
export const Post = <T>(
url: string,
data: IAnyObj = {},
params: IAnyObj = {},
config?: any
): Promise<[any, FcResponse<T> | undefined]> => {
return new Promise((resolve) => {
axios
.post(url, data, { params, ...config })
.then((result) => {
resolve([null, result.data as FcResponse<T>]);
})
.catch((err) => {
resolve([err, undefined]);
});
});
};
export const Put = <T>(
url: string,
data: IAnyObj = {},
params: IAnyObj = {}
): Promise<[any, FcResponse<T> | undefined]> => {
return new Promise((resolve) => {
axios
.put(url, data, { params })
.then((result) => {
resolve([null, result.data as FcResponse<T>]);
})
.catch((err) => {
resolve([err, undefined]);
});
});
};
export const Delete = <T>(
url: string,
params: IAnyObj = {},
data: IAnyObj = {},
config?: any
): Promise<[any, FcResponse<T> | undefined]> => {
return new Promise((resolve) => {
axios
.delete(url, { params, data, ...config })
.then((result) => {
resolve([null, result.data as FcResponse<T>]);
})
.catch((err) => {
resolve([err, undefined]);
});
});
};
api–>tools.ts
import { Local, Session } from "@/utils/storage";
import { message } from "antd";
export const handleRequestHeader = (config: any) => {
config["baseURL"] = import.meta.env.VITE_API_URL as any;
config["timeout"] = "20000";
return config;
};
export const handleAuth = (config: any) => {
config.headers["token"] = Local.get("token") || "";
return config;
};
export const handleAuthError = (errno: any) => {
const authErrMap: any = {
5000: "登陆失效,需求重新登录", //token失效
};
// Object.hasOwnProperty.call(object, key) 是用于检查一个对象(object)
// 是否具有一个指定属性(key)的方法,它返回一个布尔值。
if (Object.prototype.hasOwnProperty.call(authErrMap, errno)) {
Session.clear();
Local.clear();
window.location.reload(); //刷新当前页面
return false;
}
return true;
};
export const handleGeneralError = (errno: any, errmsg: any) => {
if (errno && errno != 200) {
message.error(errmsg ?? "未知错误");
return false;
}
return true;
};
export const handleNetworkError = (errStatus: number) => {
let errMessage = "未知错误";
const errMsgMap: any = {
400: "请求的错误",
401: "未授权,请重新登录",
403: "拒绝访问",
404: "请求错误,未找到该资源",
405: "请求方法未允许",
408: "请求超时",
500: "服务器端出错",
501: "网络未实现",
502: "网络错误",
503: "服务不可用",
504: "网络超时",
505: "http版本不支持该请求",
};
if (errStatus) {
errMessage = errMsgMap[errStatus] || `其他连接错误--${errStatus}`;
} else {
errMessage = `无法连接到服务器`;
}
message.error(errMessage);
};
基本配置已经写完的,下一步编写页面具体的逻辑…