前言
Vite 是一个超快速的前端构建工具,推动着下一代网络应用的发展。
2024 年 11 月 26 日,Vite 6.0 正式发布,标志着 Vite 迈向新阶段的重要里程碑!
提示:关于Vite的更多介绍,请移步至 官网 >>
搭建项目
环境配置:
node:v22.11.0
pnpm:v10.3.0
vite:v6.1.0
# Vue
$ pnpm create vite <PROJECT-Name> --template vue-ts
# React
$ pnpm create vite <PROJECT-Name> --template react-ts
🔖 提示
你可以使用
.作为项目名称,在当前目录中创建项目脚手架。关于如何基于 Vite 创建项目,可以参考 这里 >>。
vite.config.js
首先安装 Node.js 提供的类型定义的包 @types/node,解决 找不到模块 path 或其相对应的类型声明 的问题。
$ pnpm add -D @types/node
基本配置如下:
📖 Vue.js
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "VITE_");
return {
plugins: [vue()],
};
});
📖 React.js
import type { UserConfig, ConfigEnv } from 'vite';
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
// https://cn.vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
const env = loadEnv(mode, process.cwd(), "VITE_");
console.log(env)
return {
plugins: [react()],
};
});
取别名
@See cn.vitejs.dev/config/shar…
在 vite.config.ts 中配置 resolve.alias ,使用 @ 符号代表 src。
vite.config.js
import type { UserConfig, ConfigEnv } from 'vite';
import { defineConfig, loadEnv } from 'vite'
import { resolve } from 'node:path';
import react from '@vitejs/plugin-react'
// https://cn.vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
// -- 获取当前工作目录路径
const root = process.cwd();
const pathResolve = (path: string) => resolve(root, '.', path);
// -- 获取环境变量
const env = loadEnv(mode, root, "VITE_");
console.log(env);
return {
resolve: {
alias: {
"@": pathResolve('src'),
},
},
plugins: [react()],
};
});
tsconfig.app.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
},
}
提示:您可以根据需求配置,通常来说,配置
@/指向src/*目录即可。
开发服务器选项 & 代理
@See cn.vitejs.dev/config/serv…
server: {
host: "0.0.0.0",
port: 8888,
strictPort: false,
open: true,
cors: true,
proxy: {},
}
环境变量
@See cn.vitejs.dev/guide/env-a…
①. 在根目录新建 .env.development、.env.production、.env.test
$ touch .env.dev .env.prod .env.qa
大致内容如下:
[env.dev]
# -- 开发环境
VITE_ENV=dev;
# -- 接口请求地址
VITE_API_HOST=开发环境接口请求地址;
# -- 打包输出目录
VITE_OUT_DIR="dist"
# -- 基础路径,部署二级目录时填写,如:/my-app/
VITE_BASE=""
[.env.prod]
# -- 生产环境
VITE_ENV=prod;
# -- 接口请求地址
VITE_API_HOST=生产环境接口请求地址;
# -- 打包输出目录
VITE_OUT_DIR="dist"
# -- 基础路径,部署二级目录时填写,如:/my-app/
VITE_BASE=""
[.env.qa]
# -- 测试环境
VITE_ENV=qa;
# -- 接口请求地址
VITE_API_HOST=测试环境接口请求地址;
# -- 打包输出目录
VITE_OUT_DIR="dist"
# -- 基础路径,部署二级目录时填写,如:/my-app/
VITE_BASE=""
⚠️ 注意:
- 环境变量名必须以
VITE_开头;- 配置文件的内容请根据项目需要设置;
②. 修改 package.json 指令
为了使命令在 Windows 和 macOS/Linux 下都能正常运行,我们可以使用 cross-env。
$ pnpm add -D cross-env
📖 Vue.js
{
"scripts": {
"dev": "cross-env VITE_CJS_IGNORE_WARNING=true vite --mode dev",
"build:qa": "vue-tsc && vite build --mode qa",
"build:prod": "vue-tsc && vite build --mode prod"
}
}
📖 React.js
{
"scripts": {
"dev": "cross-env VITE_CJS_IGNORE_WARNING=true vite --mode dev",
"build:qa": "tsc --noEmit && vite build --mode qa",
"build:prod": "tsc --noEmit && vite build --mode prod"
}
}
③. 配置TypeScript 智能提示
@/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_ENV: string;
readonly VITE_API_HOST: string;
readonly VITE_OUT_DIR: string;
readonly VITE_BASE: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
④. 使用环境变量
import.meta.env.VITE_API_HOST
构建相关
@See cn.vitejs.dev/config/buil…
build: {
// -- 输出目录,默认为 dist
outDir: 'dist',
// -- 静态资源的输出目录,默认为 assets
assetsDir: 'assets',
// -- 小于此阈值的导入或引用资源将内联为 base64 编码,设置为 0 可禁用此项,单位为字节
assetsInlineLimit: 4096,
// -- 启用/禁用 CSS 代码拆分,默认为 true
cssCodeSplit: true,
// -- 构建后是否生成 source map 文件,默认为 false
sourcemap: false,
// -- 控制构建时是否清空输出目录,默认为 true
emptyOutDir: true,
// -- 控制是否压缩构建结果,默认为 true
minify: "esbuild",
// -- 设置目标浏览器,默认为 Vite 内部的默认值
target: 'modules',
// -- chunk 大小警告的限制(以 kbs 为单位)
chunkSizeWarningLimit: 2000,
// -- 启用/禁用 gzip 压缩大小报告
reportCompressedSize: false,
...
}
推荐配置:
build: {
outDir: env.VITE_OUT_DIR,
chunkSizeWarningLimit: 2000,
reportCompressedSize: false
}
gzip压缩打包
安装依赖:
$ pnpm add -D vite-plugin-compression
import { defineConfig } from "vite";
import viteCompression from "vite-plugin-compression";
export default defineConfig({
plugins: [
// -- 默认压缩 gzip,生成 .gz 文件
viteCompression({
// -- 压缩后是否删除源文件
deleteOriginFile: false,
}),
],
});
运行 pnpm build 命令,查看 dist 的 assets 目录。
提示:为了充分利用 Gzip 压缩的优势,服务器端也需要进行相应的配置,以确保能够正确地服务 Gzip 压缩文件,这一步交给后端来处理即可。
浏览器兼容
$ pnpm add @vitejs/plugin-legacy terser -D
提示:必须安装
terser,因为legacy需要使用它来进行压缩
// vite.config.js
import legacy from '@vitejs/plugin-legacy'
export default {
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
}),
],
}
打包分析可视化
$ pnpm add -D rollup-plugin-visualizer
import { defineConfig } from "vite";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
visualizer({
// -- build后,是否自动打开分析页面,默认false
open: true,
// -- 是否分析 gzip 大小
gzipSize: true,
// -- 是否分析 brotli 大小
brotliSize: true,
// -- 生成的可视化报告文件名
filename: 'stats.html',
})
],
});
使用命令 pnpm build 后,分析图 html 文件会在根目录下生成,默认命名为 stats.html。
提示:把分析文件加入
.gitignore,不提交到git仓库中
打包配置
@See cn.vitejs.dev/config/shar…
生产环境去除 console.log 、debugger
import { defineConfig } from "vite";
export default defineConfig({
esbuild: {
drop: ['debugger'],
pure: ['console.log']
}
});
项目配置
开发规范
代码规范检查与修复
代码规范:lint工具(eslint)
代码风格 prettier
1)安装
$ pnpm add -D prettier
2) 新建 .prettierrc.json 配置文件,添加配置:
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": true,
"singleQuote": true,
"semi": true,
"trailingComma": "none",
"bracketSpacing": true
}
③ 将 prettier 集成到 eslint 中,其中:
eslint-config-prettier:覆盖ESLint本身的规则配置eslint-plugin-prettier:用Prettier来接管修复代码即eslint --fix
$ pnpm add -D eslint-config-prettier eslint-plugin-prettier
④ 为 lint 增加对应的执行脚本:
"lint": "eslint --fix --quiet .",
eslint:调用 ESLint 代码检查工具。--fix:自动修复可修复的错误(例如代码格式、简单的语法问题)。--quiet:只报告错误(忽略警告 warnings),减少输出信息。.:表示检查当前目录下的所有文件。
同时为了方便,我们可以安装 prettier 与 eslint 的 VSCode 插件,并在 setting 中设置为保存后自动执行。
Editor: Default Formatter —— Prettier - Code formatter
Editor:Format On Save —— ☑️ 保存时设置文件格式
5️⃣ 测试
commit 规范检查
在 Vite 项目中,可以通过配置 commitlint + husky + lint-staged 来规范 Git 提交信息、自动执行代码质量检查,以及优化 Git Hook 执行效率。
1️⃣ 安装依赖
安装必要的依赖:
$ pnpm add -D husky lint-staged @commitlint/{cli,config-conventional}
2️⃣ 初始化 husky
初始化 husky,这会自动创建 .husky 目录并设置 Git Hook:
$ pnpm exec husky init
3️⃣ 配置 pre-commit 钩子
配置 pre-commit 钩子,在提交时运行 lint-staged,只检查暂存区的文件:
$ echo "npx lint-staged" > .husky/pre-commit && chmod +x .husky/pre-commit
在 package.json 中配置 lint-staged:
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"pnpm lint"
]
}
这样,当你执行 git commit 时,lint-staged 会自动运行 pnpm lint 来检查暂存区中的文件。
4️⃣ 配置 commit-msg 钩子
配置 commit-msg 钩子,检查提交信息是否符合规范:
$ echo "npx --no-install commitlint --edit \$1" > .husky/commit-msg && chmod +x .husky/commit-msg
创建 .commitlintrc.js 文件来配置 commitlint:
export default {
extends: ["@commitlint/config-conventional"]
};
5️⃣ 完成
现在,当你执行 git commit 时,husky 会自动触发以下钩子:
pre-commit钩子:运行lint-staged,对暂存区的文件进行代码风格检查。commit-msg钩子:运行commitlint,检查提交信息是否符合规范。
这样配置后,你的项目将能够在提交时自动进行代码风格和提交信息的检查,确保代码质量和提交信息的规范性。
6️⃣ 提示
lint-staged会自动将修改后的文件添加到暂存区,因此不要在lint-staged配置中显式调用git add。- 如果
pnpm lint检查失败,提交会被阻止。请根据错误提示修复代码后重新提交。 - 如果需要自定义提交信息规范,可以修改
.commitlintrc.js文件,添加自定义规则。
样式
less & sass
Vite 内置对 Less 和 Sass 的支持,安装对应的包后即可直接使用,无需额外配置。
# 安装 Less
$ pnpm add -D less
# 安装 Sass
$ pnpm add -D sass
tailwindcss
英文官网(推荐):tailwindcss.com/
中文官网:www.tailwindcss.cn/
- 安装 Tailwind CSS
$ pnpm add tailwindcss @tailwindcss/vite
- 配置 Vite 插件:在 vite.config.ts 配置文件中添加 @tailwindcss/vite 插件
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
...
tailwindcss(),
],
})
- 导入 Tailwind CSS:在您的 CSS 文件(通常是 index.css)中导入 Tailwind CSS 的内容
@import "tailwindcss";
🔖 提示:如果你使用 PostCSS,请参考 这里 >>
mockjs
①. 安装依赖:
$ pnpm add -D mockjs vite-plugin-mock
②. 引入插件
vite.config.ts
import { defineConfig } from "vite";
import { viteMockServe } from "vite-plugin-mock";
export default defineConfig(({ mode }) => {
return {
...
plugins: [viteMockServe()],
};
});
③. 新建 mock/test.ts 文件
// @ts-ignore
import { mock } from "mockjs";
export default [
{
url: "/api/user/:id",
method: "GET",
response: (req: any) => {
return mock({
code: 200,
data: {
id: "@guid",
name: "@cname",
avatar: "@Image('30x30','@color')",
},
msg: "success",
});
},
},
];
提示:在引入
mockjs时,我添加了@ts-ignore,因为在mockjs^1.1.0中,导出的是Mock,M是大写字母,实际在调用的时候是小写。
④. 模拟请求
fetch("api/user/1")
.then((resp) => resp.json())
.then((info) => {
console.log(info);
});
axios
①. 安装相关依赖:
$ pnpm add axios antd @likg/tools
②. 新建目录
$ mkdir -p src/api/{apiConfig,apiServer} && touch src/api/apiConfig/{index.ts,axios-type.d.ts} src/api/apiServer/{index.ts,apiExamples.ts} src/api/typings.d.ts
生成目录如下:
.
├── api
├── apiConfig
│ ├── axios-type.d.ts
│ └── index.ts
├── apiServer
│ ├── apiExamples.ts
│ └── index.ts
└── typings.d.ts
apiConfig/axios-type.d.ts
import 'axios';
declare module 'axios' {
export interface AxiosResult<T = unknown> {
code: number;
data: T;
msg: string;
}
}
apiConfig/index.ts
import Tools from "@likg/tools";
import axios, {
AxiosError,
AxiosInstance,
AxiosResponse,
AxiosResult,
AxiosRequestConfig,
InternalAxiosRequestConfig,
} from "axios";
import { message } from "antd";
// ~~~~~~~~~~~~~~~~~~~~~~~
// 错误信息
// ~~~~~~~~~~~~~~~~~~~~~~~
const errorMessages: Record<number, string> = {
400: "请求错误(400)",
401: "未授权,请重新登录(401)",
403: "拒绝访问(403)",
404: "请求出错(404)",
405: "请求方法不支持(405)",
408: "请求超时(408)",
500: "服务器异常(500)",
501: "服务未实现(501)",
502: "网络错误(502)",
503: "网络超时(503)",
504: "网络超时(504)",
505: "HTTP 版本不受支持(505)",
};
// ~~~~~~~~~~~~~~~~~~~~~~~
// 创建 axios 实例
// ~~~~~~~~~~~~~~~~~~~~~~~
const axiosInstance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_HOST,
timeout: 60 * 1000,
withCredentials: false,
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
},
});
// ~~~~~~~~~~~~~~~~~~~~~~~
// 请求拦截器
// ~~~~~~~~~~~~~~~~~~~~~~~
axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// -- 拼接token
const token = localStorage.getItem("AUTHORIZATION_TOKEN") ?? "";
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// -- 确认平台
// -- 如果同时开发支付宝/微信公众号/H5则需要传入来源
// -- 注意:具体值请与后端配合商议对应平台的source值
const platforms: Record<string, string> = {
alipay: "MP_ALIPAY",
weixin: "MP_WEIXIN",
};
const source = platforms[Tools.getEnv()] || "WEB";
config.headers.source = source;
// -- 返回配置
return config;
},
(error) => {
return Promise.reject(error);
}
);
// ~~~~~~~~~~~~~~~~~~~~~~~
// 响应拦截器
// ~~~~~~~~~~~~~~~~~~~~~~~
axiosInstance.interceptors.response.use(
async (response: AxiosResponse) => {
message.destroy();
// -- 处理流数据
if (response.request.responseType === "blob") {
return { code: 200, data: response.data, msg: "success" };
}
// -- 判断code,统一处理异常
const { code, msg } = response.data as unknown as AxiosResult;
switch (code) {
case 200:
return response.data;
case 401:
case 402:
// history.replace('/login');
return Promise.reject();
default:
message.error(msg || '请求出错啦');
return Promise.reject(msg);
}
},
(error: AxiosError) => {
console.log("[request error] > ", error);
if (error && error.response) {
const status = error.response?.status;
const errorMessage = errorMessages[status] || `连接出错(${status})!`;
error.message = errorMessage;
} else {
error.message = "服务链接失败";
}
message.error(error.message);
return Promise.reject(error);
}
);
// ~~~~~~~~~~~~~~~~~~~~~~~
// 统一的 request 方法
// ~~~~~~~~~~~~~~~~~~~~~~~
const request = <R = unknown>(options: AxiosRequestConfig): Promise<AxiosResult<R>> => {
return axiosInstance(options);
};
export default request;
typings.d.ts
declare namespace API {
// ~~~~~~~~~~~~~~~~~~
// 列表数据
// ~~~~~~~~~~~~~~~~~~
interface ListParams {
page?: number;
size?: number;
[__prop__: string]: unknown;
}
interface ListResponse<T> {
items: T[];
page: number;
size: number;
count: number;
}
}
提示:这个文件主要定义一些接口请求或响应的数据类型。
apiServer/apiExamples.ts
import request from "@/api/apiConfig";
export async function posts() {
return request<unknown>({
url: '/posts',
});
}
提示:这个文件主要定义实际业务需求中的接口,你应该根据不同的业务场景创建不同的文件,文件名义 apiXXX 开头。
apiServer/index.ts
import * as apiExamples from "./apiExamples";
export { apiExamples };
提示:这个文件主要是统一导出接口请求。
调用:
import { apiExamples } from '@/api/apiServer';
import { useEffect } from 'react';
export default function App() {
useEffect(() => {
const fetchData = async () => {
const resp = await apiExamples.posts();
console.log(resp.data);
};
fetchData();
}, []);
return <></>;
}
全局类型声明
有时,当我们引入三方SDK时,需要在window上访问,可以定义全局类型声明:
@/typings/index.d.ts
export {};
// -- 全局组件属性定义
import { FiltersProps } from "@/filters";
declare module "@vue/runtime-core" {
interface ComponentCustomProperties {
$filters: FiltersProps;
}
}
declare global {
// 👉 定义全局属性
interface Window {
/** 百度统计 */
_hmt: any;
/** 微信S*/
wx: any;
/** 百度地图 */
AMap: any;
/** 腾讯地图 */
qq: any;
/** 支付宝 */
AlipayJSBridge: any;
/** iOS回调地址 */
CONFIG_URL_FOR_IOS: string;
}
}
动态加载静态资源
在使用 webpack 动态引入静态资源可以使用 require 形式,但是在vite中不可取,会抛出 require is not defined 的错误。
不过我们可以通过 import.meta.url 的形式引入,它是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL,其语法形式如下:
new URL(url, import.meta.url)
使用示例:
const imgs = [
{ imgUrl: new URL('./assets/logo_1.png', import.meta.url), text: "标题1" },
{ imgUrl: new URL('./assets/logo_2.png', import.meta.url), text: "标题2" },
];
// => react
{imgs.map((item) => (
<img src={item.imgUrl.toString()} key={item.title} />
))}
值得注意的是,在生产环境中会抛出 “URL is not defined xxx” 的错误,这个时我们需要使用一个插件:rollup-plugin-import-meta-url-to-module。
使用方式比较简单,首先安装依赖:
$ yarn add rollup-plugin-import-meta-url-to-module
然后再 “vit.config.js” 中配置plugins:
import urlToModule from 'rollup-plugin-import-meta-url-to-module';
export default {
plugins: [
urlToModule()
]
};
移动端配置(可选)
vconsole
vconsole 是一个轻量、可拓展、针对手机网页的前端开发者调试面板。
$ pnpm add -D vconsole
main.ts
if (import.meta.env.VITE_ENV !== "prod") {
new vconsole();
}
移动端适配
$ pnpm add -D postcss-pxtorem autoprefixer
postcss.config.js
export default {
plugins: {
autoprefixer: {
overrideBrowserslist: [
"Android 4.1",
"iOS 7.1",
"Chrome > 31",
"ff > 31",
"ie >= 8",
],
},
"postcss-pxtorem": {
// -- Vant 官方根字体大小是 37.5
rootValue: 37.5,
propList: ["*"],
// -- 过滤掉.norem-开头的class,不进行rem转换
selectorBlackList: [".norem"],
},
},
};
@/utils/rem.ts
// rem 等比适配配置文件
// 基准大小,注意此值要与 postcss.config.js 文件中的 rootValue保持一致
const baseSize = 37.5;
// 设置 rem 函数
function setRem() {
// 当前页面宽度相对于375宽的缩放比例,可根据自己需要修改,一般设计稿都是宽750(图方便可以拿到设计图后改过来)。
const scale = document.documentElement.clientWidth / 375; // 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + "px";
}
// 初始化
setRem();
// 改变窗口大小时重新设置 rem
window.onresize = function () {
setRem();
};
在 mian.ts(x) 引入:
import "@/utils/rem";
如果控制台抛出:【无法在
--isolatedModules下编译rem.ts,因为它被视为全局脚本文件。请添加导入、导出或空的export {}语句来使它成为模块】此时你应该在tsconfig.json文件中将isolatedModules字段设置为false。
差异化配置
📖 Vue.js
路由配置
安装依赖:
$ pnpm add vue-router
新建文件:
$ mkdir -p src/router && touch src/router/index.ts src/router/routes.ts
routes.ts
import { RouteRecordRaw } from "vue-router";
import Layouts from "@/layouts/index.vue";
const routes: RouteRecordRaw[] = [
{
path: "/",
name: "layouts",
redirect: "/index-page",
component: Layouts,
children: [
{
path: "/index-page",
name: "index",
component: () => import("@/pages/Tabs/IndexPage.vue"),
meta: { title: "首页" },
},
{
path: "/special-sale",
name: "SpecialSale",
component: () => import("@/pages/Tabs/SpecialSale.vue"),
meta: { title: "特卖" },
},
{
path: "/friends",
name: "Friends",
component: () => import("@/pages/Tabs/Friends.vue"),
meta: { title: "好友" },
},
{
path: "/mine",
name: "Mine",
component: () => import("@/pages/Tabs/Mine.vue"),
meta: { title: "我的" },
},
],
},
{
path: "/download",
name: "Download",
component: () => import("@/pages/Download/index.vue"),
meta: {
title: "下载",
},
},
{
path: "/not-found",
name: "NotFound",
component: () => import("@/pages/NotFound/index.vue"),
},
{
path: "/:pathMatch(.*)",
redirect: "/not-found",
},
];
export default routes;
index.ts
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes";
const router = createRouter({
// -- 部署二级目录:createWebHistory(base?: string)
history: createWebHistory(import.meta.env.VITE_BASE),
// -- 路由
routes,
// -- 滚动行为
scrollBehavior: () => ({
el: "#app",
top: 0,
behavior: "smooth",
}),
});
// 导航守卫
router.beforeEach(async (to, _) => {});
router.afterEach((to) => {
// → 设置标题
if (to.path !== "/favicon.icon") {
document.title = to.meta.title ? (to.meta.title as string) : "";
}
// → 滚动
window.scrollTo(0, 0);
});
export default router;
提示:这里只是简单演示如何配置路由,实际上你应该根据你的业务需求来配置。关于 vue-router 的使用,可以参考 这里 >>
在main.ts挂载路由
import App from "@/App.vue";
import router from "@/router";
// App配置/挂载相关
// 1. 创建App
const app = createApp(App);
// 2. 注入
app.use(router);
// 3. 挂载
app.mount("#app");
状态管理
$ pnpm add pinia
提示:关于 Pinia 的使用,可以参考 这里 >>
@/stores/index.ts
import { defineStore } from 'pinia';
interface StoreProps {
count: number;
}
interface ActionProps {
increment: () => void;
decrement: () => void;
}
export const useStore = defineStore<string, StoreProps, any, ActionProps>(
'appStore',
{
state: () => ({ count: 0 }),
actions: {
async increment() {},
async decrement() {},
},
}
);
挂载:在main.ts挂载数据中心
import { createApp } from "vue";
import { createPinia } from "pinia";
import { useStore } from "@/stores";
import App from "@/App.vue";
// 👉 app
// 1. 创建app
const app = createApp(App);
// 2. 注入依赖
app.use(createPinia());
// 4. 挂在app
app.mount("#app");
// 👉 持久化pinia
const store = useStore();
// 页面进入:合并状态
const localState = localStorage.getItem("PINIA_PERSISTENCE");
if (localState) {
console.log("[温馨提示]:合并Store...");
store.$state = JSON.parse(localState);
}
// 页面刷新:存储状态
window.addEventListener("beforeunload", () => {
console.log("[温馨提示]:缓存Store...");
localStorage.setItem("PINIA_PERSISTENCE", JSON.stringify(store.$state));
});
Vant
Vant 是一个轻量、可定制的移动端组件库,Vue 首选移动端UI框架,
$ pnpm add vant
提示:配置指南,参考 这里 >>
①. 安装依赖
$ pnpm add @vant/auto-import-resolver unplugin-vue-components -D
② . 配置插件
vite.config.ts
import { defineConfig } from "vite";
import { VantResolver } from "@vant/auto-import-resolver";
import Components from "unplugin-vue-components/vite";
export default defineConfig(({ mode }) => {
return {
plugins: [
...
Components({
resolvers: [VantResolver()],
}),
],
};
});
提示:有赞团队也提供了React 版本 —— React Vant >>
二级目录部署
方案1:
打包时设置 -base 选项:
"build": "vue-tsc --noEmit && vite build --mode production --base=/二级目录名/",
然后再 router/index.ts 中配置如下:
const router = createRouter({
// 部署二级目录:createWebHistory(base?: string)
history: createWebHistory("/二级目录名/"),
routes,
});
方案2:
在 vite.config.js 配置项中添加 base 配置,如:
base:"/YOUR_PROJECT_NAME/"
然后再 router/index.ts 中配置如下:
const router = createRouter({
// 部署二级目录:createWebHistory(base?: string)
history: createWebHistory("/二级目录名/"),
routes,
});
📖 React
路由配置
①. 目录结构
.
├── components
│ ├── Fallback # 懒加载loading提示
│ ├── NotEnv # 非微信/支付宝环境提示
│ └── TabBar # 标签栏
│ │ ├── index.tsx
│ │ └── index.less
├── layouts
│ └── index.tsx
├── pages
│ ├── Auth
│ ├── IndexPage
│ ├── Integral
│ ├── Mine
│ ├── NotFound
│ └── PrivilegeBrand
├── router
│ ├── index.tsx
│ └── routes.ts
└── main.tsx
提示:这里只例举部分目录,仅供参考。
②. 安装依赖
$ pnpm add react-router-dom
提示:目前我使用的版本是
^6.23.1,关于 v6 版本的路由使用请 参考这里 >>
③. 文件代码
src/layout/index.tsx
import TabBar from '@/components/@lgs-react/TabBar';
import React from 'react';
import { Outlet } from 'react-router-dom';
const Layout: React.FC = () => {
return (
<>
{/* 视图容器,类似于vue中的 router-view */}
<Outlet />
{/* 标签栏 */}
<TabBar />
</>
);
};
export default Layout;
src/App.tsx
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Fallback from '@/components/@lgs-react/Fallback';
import Layout from '@/layout';
import IndexPage from '@/pages/IndexPage';
import Integral from '@/pages/Integral';
import Mine from '@/pages/Mine';
import PrivilegeBrand from '@/pages/PrivilegeBrand';
import Auth from '@/pages/Auth';
import NotFound from './pages/NotFound';
import NotEnv from './components/@lgs-react/NotEnv';
import Tools from 'lg-tools';
const Details = React.lazy(() => import('@/pages/Details'));
const Download = React.lazy(() => import('@/pages/Download'));
const Test = React.lazy(() => import('@/pages/Test'));
/**
* 环境判断
* 如果 VITE_APP_SOURCE === 'mp',即表示公众号/生活号
* 那么在浏览器环境将提示 “请在微信或支付宝客户端打开链接”
* @param param0
* @returns
*/
export const GuardEnv: React.FC = ({ children }) => {
return import.meta.env.VITE_APP_SOURCE === 'mp' &&
['weixin', 'alipay'].indexOf(Tools.getEnv()) === -1 ? (
<NotEnv />
) : (
<>{children}</>
);
};
/**
* appRouter
* @returns
*/
export const AppRouter: React.FC = ({ children }) => {
return (
<Suspense fallback={Fallback}>
<Router basename={import.meta.env.VITE_APP_BASE}>{children}</Router>
</Suspense>
);
};
/**
* appRoutes - 渲染路由
* @returns
*/
export const AppRoutes: React.FC = () => {
return (
<Routes>
<Route path='/' element={<Layout />}>
<Route index element={<IndexPage />} />
<Route path='privilege-brand' element={<PrivilegeBrand />} />
<Route path='integral' element={<Integral />} />
<Route path='mine' element={<Mine />} />
</Route>
<Route path='/details' element={<Details />} />
<Route path='/download' element={<Download />} />
<Route path='/test' element={<Test />} />
<Route path='/auth/:type' element={<Auth />} />
<Route path='*' element={<NotFound />} />
</Routes>
);
};
src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import ErrorBoundary from '@/components/@lgs-react/ErrorBoundary';
import '@/utils/rem';
import '@/index.css';
import { AppRouter, AppRoutes, GuardEnv } from './App';
import Schemes from 'lg-schemes';
// 1. 开发环境 & 测试环境 启用vconsole --- Tips:目前启用vconsole打包会出现异常
import vconsole from 'vconsole';
if (import.meta.env.VITE_APP_ENV !== 'pro') {
new vconsole();
}
// 2. 渲染视图
ReactDOM.render(
<React.StrictMode>
<ErrorBoundary>
<GuardEnv>
<AppRouter>
<AppRoutes />
</AppRouter>
</GuardEnv>
</ErrorBoundary>
</React.StrictMode>,
document.getElementById('root')
);
组件库
Ant Design
Ant Design 是一款基于 React 的企业级 UI 组件库,由阿里巴巴 Ant Group 开发,主要用于构建现代化、高效、优雅的中后台管理系统。它提供了丰富的 UI 组件、强大的主题定制能力,以及良好的国际化支持,是 React 生态中最受欢迎的 UI 库之一。
1)安装依赖
$ pnpm add antd
2)使用:修改 src/App.js,引入 antd 的按钮组件。
import { Button } from 'antd';
export default function App() {
return (
<div>
<Button type="primary">Button</Button>
</div>
);
}
❗️注意:
截止到发文, Ant 系列暂时还未完全适配 React 19,在使用过程中会有警告甚至错误,如下所示:
Warning: [antd: compatible] antd v5 support React is 16 ~ 18. see u.ant.design/v5-for-19 for compatible.
官方已经推出了兼容方案(参考 这里 >>),就是安装兼容包 @ant-design/v5-patch-for-react-19
3)设置组件默认语言为中文
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN'; // 引入中文语言包
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
</React.StrictMode>
);Ï
Ant Design Mobile
Ant Design Mobile 是 Ant Design 的移动端版本,它继承了 Ant Design 优秀的设计理念,拥有丰富的高质量移动端组件,提供良好的交互体验和视觉效果,支持 React 和 Vue 等多种技术栈,方便集成到现有项目中,并且注重性能优化与兼容性,适合用于构建企业级移动端应用,能让开发保持统一设计风格且高效。
$ pnpm add antd-mobile
💡提示:配置指南,参考 这里 >>
二级目录部署
①. 在 package.json 文件中配置指令时时设置 --base=/二级目录名/。
②. 在各环境配置文件中根据需要设置 VITE_APP_BASE 字段值。
②. 在配置路由时设置 basename 属性,如下所示:
/**
* src/router/index.tsx
* appRouter
* @returns
*/
export const AppRouter: React.FC = ({ children }) => {
return (
<Suspense fallback={Fallback}>
<Router basename={import.meta.env.VITE_APP_BASE}>{children}</Router>
</Suspense>
);
};
模板地址
闲暇之余,结合 Vite 封装了两个模板,大家可以参考:
提示:本文中的代码片段均摘自上述模板文件。