背景:如今前端框架多、版本多、迭代快。以 vue 为例,就有 vue2/vue3,依赖管理工具就有 npm/yarn,打包工具就有 webpack/vite,UI 框架就有 antd-vue/element_ui/element_ui_plus,没有统一的路径可循,影象心智负担较重。
为了方便后续快速搭建项目,能利用最新的框架,而且减轻心智负担,本文采用 vue3 + yarn + vite + antd-vue,方便快速上手。
教程
一、项目搭建
1.1 生态工具对比
包管理 pnpm(npm、yarn)
- 最早是 npm2,其node_modules 有多层嵌套问题,导致文件路径凌驾 windows 最长路径限定
- 之后 yarn 解决了上述路径问题,将 node_modules 全部平铺,但仍有如下问题:
- 幽灵依赖问题:即旧版本的第三方库B1引用了C库,我们即可在项目A中用 require 引用 C 库。但当B1升级到新版本B2后,不再依赖C库了,C库即会被从 node_modules 中移除。但我们本身的代码A就会报错找不到C库的错误
- 某库的多版本:占用多份磁盘空间。例如我们本身的项目A引用了C库的C1版本,我们引用的B库又依赖于C库的C2版本。那么 node_modules 中就同时存在 C1 和 C2 两个版本,浪费磁盘空间。
- 同时,npm 3 也跟进了上述 yarn 的盼望
- 最新的 pnpm 则完全解决了上述问题,有如下两个优点:
- 小:其整个电脑的所有项目共用同一份依赖文件(例如将C库的C1和C2两个版本都放在电脑全局的 ~/CommonStore文件夹下,然后A项目的各依赖文件都用软链接or硬链接指向全局路径)。这样就大大节省了各项目A、X、Y之间共用的B库和C库的冗余磁盘占用(究竟只存一份嘛)。
- 快:因为是链接形式不需要拷贝,所以速度快。
pnpm 是凭什么对 npm 和 yarn 降维打击的
pnpm官网
打包工具 vite (webpack)
- 生产环境速度相当,但开辟环境 vite 比 webpack 快多了, 因为vite分阶段编译(用户点击哪个路由,才编译哪个模块),而 webpack 需要全部编译完。vite 在开辟阶段显着减少了编译等待时间(1min到1s的提升幸福感)。
- 若用 vite 初始化项目则配置文件为 vite.config.ts,若用 vue-cli 初始化项目则配置文件为 vue.config.js,配置内容近似。
1.2 项目创建
vite 项目搭建教程
vite 官网
1.2.0 项目搭建
此中各下令的表明, 详见下文
- nvm install 22
- nvm alias default v22.14.0
- nvm use 22
- node -v # v22.14.0 (nvm ls 可查看/选择版本)
- npm install -g corepack # 升级最新版 corepack
- corepack -v # 查 corepack 版本 0.31.0
- corepack enable # 安装 pnpm
- corepack prepare pnpm@latest --activate # 升级 pnpm 到最新版本
- pnpm -v # 10.5.2
- pnpm create vite fe --template vue-ts # 初始化项目
- cd fe && pnpm install && pnpm run dev # 安装依赖并run
- pnpm add @types/node --save-dev # 为保证 node 的使用. 这是 Node.js 的 TypeScript 类型定义文件. 详见下文
复制代码 1.2.1 node 版本
- # 安装 nvm (~/.zshrc 可配置 nvm 的环境变量如下:)
- # nvm
- export NVM_DIR="$HOME/.nvm"
- [ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm
- [ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion
- nvm use 22
- nvm ls
复制代码 node 版本, 不会对整套技术栈有太大影响, 反而建议尽量新, 这样可以支持更多特性:
- Vue3 对 Node.js 版本的依赖较低,重要依赖于浏览器的兼容性。Node.js 20 和 22 都不会对 Vue3 的兼容性产生显着影响。
- Ant Design Vue 是一个 UI 组件库,与 Node.js 版本的直接关联较小。只要 Node.js 版本支持 ES Module 和 CommonJS,就不会有兼容性问题。
- TypeScript 的编译和运行与 Node.js 版本的关联较小。Node.js 20 和 22 都支持 TypeScript 的最新特性。
- Vite 4 依赖于 Node.js 的模块系统和文件系统 API。Node.js 20 和 22 都会支持 Vite 4 的焦点功能,但建议使用较新的 Node.js 版本以获得更好的性能和稳固性。
- PNPM 是一个包管理工具,与 Node.js 版本的兼容性较好。Node.js 20 和 22 都支持 PNPM 的最新特性。
- Pinia 是 Vue 的状态管理库,与 Node.js 版本的直接关联较小。只要 Node.js 版本支持 ES Module,就不会有兼容性问题。
1.2.2 corepack
Corepack 是 Node.js 内置的一个包管理工具管理器(Package Manager Manager),用于管理差别的 JavaScript 包管理工具(如 npm、yarn、pnpm 等)。它的重要目的是简化包管理工具的安装和版本管理,确保开辟者在差别项目中使用同等的包管理工具版本。
Corepack 从 Node.js 16.9.0 开始默认启用,并在后续版本中逐步增强。
corepack enable 是一个下令,用于启用 Corepack 的功能。具体来说:运行 corepack enable 后,Corepack 会在系统中启用,并准备好管理包管理工具。启用后,你可以直接使用 yarn 或 pnpm 等下令,而无需手动安装这些工具。
默认 Node.js 16.9.0 及更高版本中,Corepack 默认是启用的,因此通常不需要手动运行 corepack enable。
示例:
启用 Corepack:corepack enable
安装特定版本的包管理工具:corepack prepare yarn@1.22.19 --activate
在项目中使用指定的包管理工具:在 package.json 中指定包管理工具及其版本:
- {
- "packageManager": "yarn@1.22.19"
- }
复制代码 然后运行 yarn 或 pnpm 时,Corepack 会自动使用指定的版本
1.2.3 npm install -g
npm install -g vite @vue/cli 安装的是 下令行工具(CLI), 而不是 nodejs 的代码库.
- 下令行工具(CLI):
vite 和 @vue/cli 都是下令行工具,用于快速创建、构建和管理项目。
安装后,你可以在终端中直接运行 vite 或 vue 下令来实行相关操作。
- 全局安装:
-g 表示全局安装(–global),这些工具会被安装到系统的全局 node_modules 目录中,而不是当前项目的 node_modules。
全局安装后,你可以在任何目录下使用这些下令。
- vite 工具的使用:
一个现代化的前端构建工具,用于快速启动和开辟项目(尤其是 Vue、React 等框架)。
安装后,你可以使用 vite 下令来创建新项目、启动开辟服务器或构建生产版本。
示例:
- vite create my-project
- cd my-project
- vite dev
复制代码
- @vue/cli 工具的使用:
Vue.js 的官方下令行工具,用于快速搭建 Vue 项目。
安装后,你可以使用 vue 下令来创建、管理和构建 Vue 项目。
示例:
vue create my-project
cd my-project
vue serve
- 安装后的位置
全局安装的 cli 工具会被安装到 全局 node_modules 目录中, mac 为 ~/.nvm/versions/node/<版本>/lib/node_modules. 另外这些工具的可实行文件会被链接到系统的 PATH 中, 所以可以直接在终端实行 vite 或 vue 下令.
npm install 可加/不加 -g 参数
pnpm add 可加/不加 -g 参数
上述二者, 都既可以安装 下令行 cli, 也可以安装代码库.
如果一个项目的 package.json 中含 “bin” 则表示为 下令行 cli, 否则表示为代码库
例如 vite 的 package.json 如下:
- {
- "name": "vite",
- "bin": {
- "vite": "bin/vite.js"
- }
- }
复制代码 例如 lodash 的 package.json 如下:
- {
- "name": "lodash",
- "main": "lodash.js"
- }
复制代码 如果下令行, 未指定 -g, 则被安装在本项目的 node_modules 目录中(而不在全局 node_modules 目录中), 则可通过 npx 运行, 例如 npx vite
1.2.4 pnpm
配置方式:
- Appended new lines to /Users/y/.zshrc
- Next configuration changes were made:
- export PNPM_HOME="/Users/y/Library/pnpm"
- case ":$PATH:" in
- *":$PNPM_HOME:"*) ;;
- *) export PATH="$PNPM_HOME:$PATH" ;;
- esac
- To start using pnpm, run:
- source /Users/y/.zshrc
复制代码 升级最新版 pnpm self-update, (注意项目的 package.json 文件中写死了 pnpm 的旧版本号, 可手动改该文件, 或切换到非项目目录再 pnpm -v).
检察版本 pnpm -v
pnpm store path 可检察其全局存储路径:
- # pnpm store path
- /Users/abc/Library/pnpm/store/v3
- # du -sh /Users/abc/Library/pnpm/store/v3
- 335M /Users/abc/Library/pnpm/store/v3
复制代码 安装生产依赖:
如果包是项目运行所必需的,则可不填 (或 --save)(或 -S):pnpm add lodash
全局安装:
如果包需要全局使用,使用 -g:pnpm add typescript -g
删除包:
如果需要删除包,使用 uninstall:pnpm uninstall @types/node --save-dev
1.2.5 pnpm add @types/node --save-dev
为包管 node 的使用. 这是 Node.js 的 TypeScript 类型定义文件.
–save-dev 是把包作为 开辟依赖 devDependencies 安装(而不是作为生产依赖).
pnpm 会从 npm 堆栈下载 @types/node 包,并将其安装到项目的 node_modules 目录中. @types/node 会被添加到 package.json 的 devDependencies 字段中.
- {
- "devDependencies": {
- "@types/node": "^20.0.0"
- }
- }
复制代码 安装后,TypeScript 编译器会自动识别 Node.js 的类型,避免类型错误
使用场景:
当你使用 TypeScript 开辟 Node.js 项目时,需要安装 @types/node 来获得 Node.js API 的类型支持。
例如,在代码中使用 fs 模块时,TypeScript 会检查 fs.readFile 的参数和返回值类型。
示例:
假设你在开辟一个 Node.js + TypeScript 项目,代码中使用了 fs 模块:
- import fs from 'fs';
- fs.readFile('file.txt', 'utf-8', (err, data) => {
- if (err) throw err;
- console.log(data);
- });
复制代码 如果没有安装 @types/node,TypeScript 会报错,因为它无法识别 fs 模块的类型。安装 @types/node 后,TypeScript 就能正确识别 fs 的类型。
1.2.6 pnpm create vite
大概有一种征象: vite -v 和 vue -v 都无输出(说明没安装), 但 pnpm create vite fe --template vue-ts 却可以正常创建 vite-vue-ts 的项目.
- vite -v 和 vue -v 无输出, 是因为没有全局安装.
- pnpm create vite 是一个复合下令, 在其内部会下载并运行 create-vite 这个工具, 但不会全局安装 vite. 而是会在项目内安装(即在项目内的 node_modules 目录内), 即会在 package.json 的 devDependencies 中 配置如下:
- "devDependencies": {
- "vite": "^6.2.0"
- }
复制代码 在项目中, pnpm vite -v 即可运行本地安装的 vite
- # pnpm vite -v
- vite/6.2.0 darwin-arm64 node-v20.18.3
复制代码
- 建议在项目内安装 vite, 而不是在全局安装 vite. 这样避免因全局差别版本导致的兼容问题. 项目内可通过 package.json 管理, 便于团队协作版本控制.
1.3 集成配置
tsconfig.json
tsconfig.json 就用 脚手架 生成的即可, 无需修改
最新的脚手架分为如下三个文件:
tsconfig.json 如下:
- {
- "files": [],
- "references": [
- { "path": "./tsconfig.app.json" },
- { "path": "./tsconfig.node.json" }
- ]
- }
复制代码 tsconfig.app.json 如下:
- {
- "extends": "@vue/tsconfig/tsconfig.dom.json",
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
- }
复制代码 tsconfig.node.json 如下:
- {
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2022",
- "lib": ["ES2023"],
- "module": "ESNext",
- "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "isolatedModules": true,
- "moduleDetection": "force",
- "noEmit": true,
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["vite.config.ts"]
- }
复制代码 vite.config.ts
修改 vite.config.ts 参考. 此中关键是指定 @ 为 src 目录的别名.
(可选) 新建 /src/assets/styles/variables.scss 用于放公共的 scss 变量,其可被 .scss 文件或 .vue 文件 引用。
- pnpm add mockjs -S # 用于生成随机数据的 JavaScript 库,主要用于前端开发中的模拟数据(Mock Data)
- pnpm add vite-plugin-mock -D # 是一个 Vite 插件,用于在 Vite 项目中集成 Mock 数据功能
- pnpm add postcss-px-to-viewport -S # 是一个 PostCSS 插件,用于将 CSS 中的 px 单位转换为 viewport 单位(如 vw、vh),以实现移动端自适应布局
复制代码- // vite.config.ts
- import { defineConfig } from 'vite'
- import vue from '@vitejs/plugin-vue'
- import { viteMockServe } from "vite-plugin-mock";
- import * as path from "path";
- let backendAddr = process.env.BACKEND_ADDR || 'http://192.168.2.180:334';
- export default defineConfig({
- // base: "/foo/", // 开发或生产环境服务的公共基础路径
- base: "/", // 开发或生产环境服务的公共基础路径
- optimizeDeps: {
- force: true, // 强制进行依赖预构建
- },
- css: {
- preprocessorOptions: {
- scss: {
- additionalData: `@use '/src/assets/styles/variables.scss';`, // 引入全局变量文件
- },
- },
- },
- resolve: {
- alias: {
- "@": path.resolve(__dirname, "./src"), // 路径别名
- },
- extensions: [".js", ".ts", ".json"], // 导入时想要省略的扩展名列表
- },
- server: {
- host: true, // 监听所有地址
- proxy: {
- // // 字符串简写写法
- "/foo": "http://localhost:4567",
- // // 选项写法
- "^/api": {
- target: backendAddr,
- changeOrigin: true,
- rewrite: (path: string) => path.replace(/^\/api/, ""),
- },
- "^/ws": {
- target: backendAddr,
- changeOrigin: true,
- rewrite: (path: string) => path.replace(/^\/ws/, ""),
- }
- },
- },
- build: {
- outDir: "dist", // 打包文件的输出目录
- assetsDir: "static", // 静态资源的存放目录
- assetsInlineLimit: 4096, // 图片转 base64 编码的阈值
- },
- plugins: [vue(), viteMockServe()],
- });
复制代码 启动脚本
通常,本地启动前端后,需要连本机环境和联调环境(和后端同砚调接口),则可在 vite.config.js 中设置如下(上文已包含):
- let backendAddr = process.env.BACKEND_ADDR || 'http://192.168.2.99:334'; // 若有环境变量则连本地后端服务,若无则连联调机器的后端服务
- // 并在 server.proxy 中引用该变量即可,示例如下:
- server: {
- host: true, // 监听所有地址
- proxy: {
- "^/ws": {
- target: backendAddr,
- changeOrigin: true,
- rewrite: (path) => path.replace(/^\/ws/, ""),
- },
- }
复制代码 则指定环境变量启动即可连本地后端服务:
- BACKEND_ADDR='http://127.0.0.1:9999' pnpm run dev
复制代码 eslint(可选)
- pnpm add eslint eslint-plugin-vue --save-dev
- pnpm add @typescript-eslint/parser --save-dev
复制代码 创建配置文件: .eslintrc.js
- module.exports = {
- parser: 'vue-eslint-parser',
- parserOptions: {
- parser: '@typescript-eslint/parser',
- ecmaVersion: 2020,
- sourceType: 'module',
- ecmaFeatures: {
- jsx: true
- }
- },
- extends: [
- 'plugin:vue/vue3-recommended',
- 'plugin:@typescript-eslint/recommended',
- ],
- rules: {
- // override/add rules settings here, such as:
- }
- };
复制代码 创建忽略文件:.eslintignore
- node_modules/
- dist/
- index.html
复制代码 下令行式运行:修改 package.json
- {
- ...
- "scripts": {
- ...
- "eslint:comment": "使用 ESLint 检查并自动修复 src 目录下所有扩展名为 .js 和 .vue 的文件",
- "eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
- }
- ...
- }
复制代码 集成 prittier(可选)
- pnpm add prettier eslint-config-prettier eslint-plugin-prettier --save-dev
复制代码 创建配置文件: prettier.config.js 或 .prettierrc.js
- module.exports = {
- // 一行最多 80 字符
- printWidth: 80,
- // 使用 4 个空格缩进
- tabWidth: 4,
- // 不使用 tab 缩进,而使用空格
- useTabs: false,
- // 行尾需要有分号
- semi: true,
- // 使用单引号代替双引号
- singleQuote: true,
- // 对象的 key 仅在必要时用引号
- quoteProps: "as-needed",
- // jsx 不使用单引号,而使用双引号
- jsxSingleQuote: false,
- // 末尾使用逗号
- trailingComma: "all",
- // 大括号内的首尾需要空格 { foo: bar }
- bracketSpacing: true,
- // jsx 标签的反尖括号需要换行
- jsxBracketSameLine: false,
- // 箭头函数,只有一个参数的时候,也需要括号
- arrowParens: "always",
- // 每个文件格式化的范围是文件的全部内容
- rangeStart: 0,
- rangeEnd: Infinity,
- // 不需要写文件开头的 @prettier
- requirePragma: false,
- // 不需要自动在文件开头插入 @prettier
- insertPragma: false,
- // 使用默认的折行标准
- proseWrap: "preserve",
- // 根据显示样式决定 html 要不要折行
- htmlWhitespaceSensitivity: "css",
- // 换行符使用 lf
- endOfLine: "auto",
- };
复制代码 修改 .eslintrc.js 配置
- module.exports = {
- ...
- extends: [
- 'plugin:vue/vue3-recommended',
- 'plugin:@typescript-eslint/recommended',
- 'prettier',
- 'plugin:prettier/recommended'
- ],
- ...
- };
复制代码 下令行式运行:修改 package.json
- {
- ...
- "scripts": {
- ...
- "prettier:comment": "自动格式化当前目录下的所有文件",
- "prettier": "prettier --write"
- }
- ...
- }
复制代码 效果如下:

pinia
踩坑教训: 不可用时, 先检查是否 pnpm add 乐成了, (因为 vscode 提示时好时坏)
- pnpm add pinia # 若报错, 则可先停止 pnpm run dev 再执行
复制代码 新建 src/store 目录并在其下面创建 index.ts,导出 store
- import { createPinia } from 'pinia'
- const store = createPinia()
- export default store
复制代码 在 main.ts 中引入并使用
- import { createApp } from "vue";
- import "./style.css";
- import App from "./App.vue";
- import store from "./store";
- const app = createApp(App); // 创建vue实例
- app.use(store); // 挂载pinia
- app.mount("#app"); // 挂载实例
复制代码 定义State: 在 src/store 下面创建一个 user.ts
- import { defineStore } from "pinia";
- export const useUserStore = defineStore("user", {
- state: () => {
- return {
- name: "张三",
- };
- },
- actions: {
- updateName(name: string) {
- this.name = name;
- },
- },
- });
复制代码 获取和修改 State: 在 src/components/usePinia.vue 中使用
- <template>
- <div>{{ userStore.name }}</div>
- </template>
- <script lang="ts" setup>
- import { useUserStore } from "@/store/user";
- const userStore = useUserStore();
- userStore.updateName("张三");
- </script>
复制代码 注意, 因为此中用到了 “@”, 所以需在 tsconfig.json 或 tsconfig.app.json 中定义如下:
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "@/*": ["src/*"]
- }
- }
复制代码 集成 vue-router4
新建 src/router 目录并在其下面创建 index.ts,导出 router
- import { createRouter, createWebHashHistory } from 'vue-router';
- import type { RouteRecordRaw } from 'vue-router';
- const routes: Array<RouteRecordRaw> = [
- // {
- // path: "/login",
- // name: "Login",
- // meta: {
- // title: "登录",
- // keepAlive: true,
- // requireAuth: false,
- // },
- // component: () => import("@/pages/login.vue"),
- // },
- {
- path: "/aa",
- name: "VueUse",
- meta: {
- title: "鼠标",
- keepAlive: true,
- requireAuth: true,
- },
- component: () => import("@/pages/vueUse.vue"), // 组件的 懒加载
- },
- // {
- // path: "/hello",
- // name: "HelloWorld",
- // meta: {
- // title: "计数器",
- // keepAlive: true,
- // requireAuth: true,
- // },
- // component: () => import("@/components/HelloWorld.vue"),
- // },
- // {
- // path: "/request",
- // name: "request",
- // meta: {
- // title: "请求页",
- // keepAlive: true,
- // requireAuth: true,
- // },
- // component: () => import("@/pages/request.vue"),
- // }
- ];
- const router = createRouter({
- history: createWebHashHistory(),
- routes,
- });
- export default router;
复制代码 在 main.ts 中引入 import router from '@/router'; 并使用
- import { createApp } from "vue";
- import "./style.css";
- import App from "./App.vue";
- import store from "./store";
- import router from "./router"; // 或 '@/router'
- const app = createApp(App); // 创建vue实例
- app.use(store).use(router); // 挂载组件
- app.mount("#app"); // 挂载实例
复制代码 修改 App.vue
- <script setup lang="ts">
- import HelloWorld from "./components/HelloWorld.vue";
- </script>
- <template>
- <h1>abc</h1>
- <div>123</div>
- <HelloWorld msg="def" />
- <RouterView />
- </template>
- <style>
- #app {
- font-family: Avenir, Helvetica, Arial, sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- text-align: center;
- color: #2c3e50;
- margin-top: 60px;
- }
- </style>
复制代码 集成 vueUse
VueUse 是一个基于 Composition API 的实用函数聚集。
新建 src/pages/vueUse.vue 如下:useMouse 只是 vueuse 的一个最根本的函数库
- <template>
- <h1>测试 use 鼠标坐标</h1>
- <h3>Mouse: {{ x }} x {{ y }}</h3>
- </template>
- <script lang="ts">
- import { defineComponent } from "vue";
- import { useMouse } from "@vueuse/core";
- export default defineComponent({
- name: "VueUse",
- setup() {
- const { x, y } = useMouse();
- return {
- x,
- y,
- };
- },
- });
- </script>
复制代码 localhost:5173 如下:
localhost:5173/aa 如下:
还有很多,总会有一个适合你;更多函数官方文档
localhost:/5173/hello 如下:
集成 sass
新建 src/assets/styles/variables.scss 文件, 内容如下:
- $blue: #007bff;
- $red: #dc3545;
- $primary-color: red;
- $secondary-color: blue;
复制代码 使用在 .vue 文件, 因为上文已在 vite.config.ts 中 @use '/src/assets/styles/variables.scss';, 所以直接使用即可. 例如设置 color 为 red:
- <template>
- <h1>测试 use 鼠标坐标</h1>
- <h6 class="c1">{{ x }} x {{ y }}</h6>
- </template>
- <script lang="ts">
- import { defineComponent } from "vue";
- import { useMouse } from "@vueuse/core";
- export default defineComponent({
- name: "VueUse",
- setup() {
- const { x, y } = useMouse();
- return {
- x,
- y,
- };
- },
- });
- </script>
- <style lang="scss">
- .c1 {
- color: blue; // 亲测, red/blue/yellow等不需要自己定义, 默认已经有了. 其实在 vite.config.ts 里已经 @use '/src/assets/styles/variables.scss 了, 可以再其中再定义变量, 如 $blue 等
- }
- </style>
复制代码 localhost://5173 效果如下:
集成 axios
axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
新建 src/utils/axios.ts
- import axios from 'axios';
- import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
- const service = axios.create();
- // Request interceptors
- service.interceptors.request.use(
- (config: InternalAxiosRequestConfig) => {
- // do something
- return config;
- },
- (error: any) => {
- return Promise.reject(error);
- }
- );
- // Response interceptors
- service.interceptors.response.use(
- async (response: AxiosResponse) => {
- console.log("response", response);
- // do something
- return response;
- },
- (error: any) => {
- // do something
- return Promise.reject(error);
- }
- );
- export default service;
复制代码 在页面中使用即可, 此中涉及 await 调接口, setup() 的机会调用, 调用后赋值给变量, 并在 <template> 内渲染, 完整 vue 代码和关键步骤如下:

- <template>
- <h1>测试 use 鼠标坐标</h1>
- <h2 v-if="d">接口响应结果为 {{ d }}</h2>
- <h3 class="c1">Mouse: {{ x }} x {{ y }}</h3>
- </template>
- <script lang="ts">
- import { defineComponent, ref } from "vue";
- import { useMouse } from "@vueuse/core"; // 安装后该引用将正常解析
- import request from '@/utils/axios';
- export default defineComponent({
- name: "VueUse",
- setup() {
- const d = ref<any>(null); // 定义响应式变量, 用于存接口返回的数据
- const requestRes = async () => {// 定义函数, 其调接口
- try {
- const result = await request({
- url: '/api/abc-server/ping',
- method: 'get'
- })
- console.log(result);
- d.value = result.data; // 把接口响应的数据, 赋值给, 响应式变量
- } catch (error) {
- console.error("请求失败", error);
- }
- }
- requestRes(); // 组件初始化时, 调函数
- const { x, y } = useMouse(); // 将正确获取鼠标坐标
- return { x, y, d }; // 所有组件内的 变量, 都要 return 出去, template 才能使用
- },
- });
- </script>
- <style lang="scss">
- @import '@/assets/styles/variables.scss';
- .c1 {
- color: blue;
- }
- </style>
复制代码 其对应的 vite.config.ts 的关键配置如下, 具体内容上文有先容.

用 go 写个 http server 如下:
- func main() {
- http.HandleFunc("/ping", Ping)
- http.ListenAndServe(":9999", nil)
- }
- func Ping(w http.ResponseWriter, req *http.Request) {
- fmt.Fprintf(w, "pong\n")
- }
- // curl --location --request GET 'http://localhost:9999/ping'
- pong
复制代码 封装哀求参数和相应数据的所有 api
新建 src/api/index.ts:
- import * as login from './module/login';
- import * as ping from './module/ping';
- export default Object.assign({}, login, ping);
- /*
- 其中 `import * as` 将 ./module/login 模块中的所有导出内容, 作为一个命名空间对象导入。
- 例如,如果 login.ts 导出了 loginUser 和 logoutUser 两个函数,那么 login 对象将包含这两个函数:
- login = {
- loginUser: Function,
- logoutUser: Function,
- };
- */
- /*
- Object.assign 是 JavaScript 的方法,用于将一个或多个对象的属性合并到目标对象中。
- 第一个参数是目标对象(这里是空对象 {}),后面的参数是源对象(这里是 login 和 index)。
- 合并后,目标对象将包含 login 和 index 的所有属性。
- 例如若
- login = {
- loginUser: Function,
- logoutUser: Function,
- };
- index = {
- getData: Function,
- setData: Function,
- };
- 则合并后的对象为
- {
- loginUser: Function,
- logoutUser: Function,
- getData: Function,
- setData: Function,
- }
- 属性冲突的情况:
- 其中, 如果 login 和 index 中有同名属性,Object.assign 会以后面的对象为准。例如:
- login = { foo: 'login' };
- index = { foo: 'index' };
- 则 Object.assign({}, login, index); // { foo: 'index' }
- 浅拷贝:
- Object.assign 是浅拷贝,如果属性值是对象,拷贝的是引用而不是值。
- */
- /*
- export default 将 Object.assign({}, login, index) 的结果作为默认导出。
- 其他模块可以通过 import combined from './path/to/module'; 导入这个合并后的对象
- */
复制代码 新建 src/api/module/login.ts
- import request from '@/utils/axios';
- // 登录
- // model
- interface IResponseType<P = {}> {
- code?: number;
- status: number;
- msg: string;
- data: P;
- }
- interface ILogin {
- token: string;
- expires: number;
- }
- // function
- export const login = (username: string, password: string) => {
- return request<IResponseType<ILogin>>({
- url: '/api/auth/login',
- method: 'post',
- data: {
- username,
- password
- }
- });
- };
复制代码 新建 src/api/module/ping.ts
- import request from '@/utils/axios';
- // function
- export const ping = () => {
- return request<String>({
- url: '/api/auth/login',
- method: 'get'
- });
- };
复制代码 由于使用了 typescript,所以需新增 src/types/shims-axios.d.ts
- import { AxiosRequestConfig } from 'axios';
- // 自定义扩展axios模块
- declare module 'axios' {
- export interface AxiosInstance {
- <T = any>(config: AxiosRequestConfig): Promise<T>;
- request<T = any>(config: AxiosRequestConfig): Promise<T>;
- get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
- delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
- head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
- post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
- put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
- patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
- }
- }
复制代码 新建 src/pages/request.vue 页面,并在此中使用
- <template>
- <h2>这是 request 请求页</h2>
- <br />
- <router-link to="/">点击跳转至首页</router-link>
- <button @click="requestRes()"></button>
- </template>
- <script lang="ts">
- import { defineComponent } from 'vue';
- import request from '@/utils/axios';
- import API from "@/api";
- export default defineComponent({
- name: "RequestPage",
- setup() {
- const requestRes = async () => {
- let result = await request({
- url: "/api/corgi/ping",
- method: "get,"
- })
- console.log(result);
- }
- const requestResAPI = async () => {
- let result = await API.login("zhangsan", "123456");
- console.log(result);
- }
- return {
- requestRes,
- requestResAPI
- }
- }
- })
- </script>
复制代码 效果如下:
引入 antdvue
AntdVue,引入方式参考官网
- pnpm add ant-design-vue
- pnpm add vite-plugin-style-import --save-dev # 非必须
- pnpm add vue-svg-icon
复制代码 在 main.ts 全局完整引入如下:
- import { createApp } from 'vue'
- import './style.css'
- import "ant-design-vue/dist/reset.css";
- import Antd from 'ant-design-vue';
- import App from './App.vue'
- import store from './store'
- import router from './router'
- createApp(App).use(router).use(store).use(Antd).mount('#app')
复制代码 需要注意的是,样式文件需要单独引入。
使用 antdvue
新建 test.vue,此中用到了 <a-button> 标签
- <template>
- <h2>测试</h2>
- <a-button type="primary">添加用户</a-button>
- </template>
- <script lang="ts">
- import { defineComponent } from "vue";
- import request from "@/utils/axios";
- import API from "@/api";
- export default defineComponent({
- name: "RequestPage",
- setup() {
- const requestRes = async () => {
- let result = await request({
- url: "/api/corgi/ping",
- method: "get",
- });
- console.log(result);
- };
- const requestResAPI = async () => {
- let result = await API.login("zhangsan", "123456");
- console.log(result);
- };
- return {
- requestRes,
- requestResAPI,
- };
- },
- });
- </script>
复制代码 效果如下,说明跑通了
二、vue 用法
2.1 动画
2.1.1 原生 css 动画
transition
- <template>
- <div class="box" :style="{ width: width + 'px' }"></div>
- <button @click="change">click</button>
- </template>
- <script lang="ts" setup>
- import { ref } from "vue";
- let width = ref(100);
- function change() {
- width.value += 100;
- }
- </script>
- <style>
- .box {
- background: red;
- height: 100px;
- }
- </style>
复制代码 效果如下:
为了优化效果,可以把样式改为如下,即 width 属性需要线性过度,时间为 1s:
- <style>
- .box {
- background: red;
- height: 100px;
- transition: width 1s linear;
- }
- </style>
复制代码 效果如下:

animation
- <template>
- <div class="box" :style="{ width: width + 'px' }"></div>
- <button @click="change">click</button>
- </template>
- <script lang="ts" setup>
- import { ref } from "vue";
- let width = ref(30);
- function change() {
- width.value += 100;
- }
- </script>
- <style>
- .box {
- width: 30px;
- height: 30px;
- position: relative;
- background: #d88986;
- animation: move 2s linear infinite; /*持续 2s,线性变化,无限循环*/
- /*
- move:指定动画的名称,对应 @keyframes move 定义的动画。
- 2s:动画的持续时间为 2 秒。
- linear:动画的时间函数为线性(匀速)。
- infinite:动画无限循环。
- */
- }
- /* 定制动画在0%,50%,100%的位置 */
- @keyframes move {
- 0% {
- left: 0px;
- }
- 50% {
- left: 200px;
- }
- 100% {
- left: 0;
- }
- }
- /*
- @keyframes 定义了动画的关键帧,具体含义如下:
- 0%:动画开始时,元素的 left 值为 0px。
- 50%:动画进行到一半时,元素的 left 值为 200px。
- 100%:动画结束时,元素的 left 值回到 0px。
- 3. 动画效果
- 元素 .box 会从初始位置(left: 0px)向右移动到 left: 200px,然后再回到初始位置(left: 0px)。
- 整个动画持续 2 秒,匀速运动,并且无限循环。
- 4. 代码的完整行为
- 初始状态:
- .box 的宽度为 30px,高度为 30px,背景色为 #d49d9b。
- 动画开始前,元素位于 left: 0px。
- 动画过程:
- 在 0% 时,元素位于 left: 0px。
- 在 50% 时(1 秒后),元素移动到 left: 200px。
- 在 100% 时(2 秒后),元素回到 left: 0px。
- 循环:
- 动画完成后,重新开始,无限循环。
- 按钮点击:
- 点击按钮时,.box 的宽度会增加 100px,但动画效果不受影响,继续按照 @keyframes 的定义运行。
- 5. 可视化效果
- 你会看到一个宽度为 30px 的方块,在水平方向上左右移动:
- 从最左侧(left: 0px)向右移动到 left: 200px,然后再回到最左侧。
- 每次点击按钮,方块的宽度会增加 100px,但动画的移动范围(left: 0px 到 left: 200px)不变。
- */
- </style>
复制代码 效果如下:

2.1.2 vue 动画
- <template>
- <button @click="toggle">click</button>
- <transition name="fade">
- <h1 v-if="showTitle">你好</h1>
- </transition>
- </template>
- <script lang="ts" setup>
- import { ref } from "vue";
- let showTitle = ref(true);
- function toggle() {
- showTitle.value = !showTitle.value;
- }
- </script>
- <style>
- .fade-enter-active,
- .fade-leave-active {
- transition: opacity 0.5s linear;
- }
- .fade-enter-from,
- .fade-leave-to {
- opacity: 0;
- }
- </style>
复制代码 vue 的 transition 约定如下:
效果如下:
2.2 jsx
简单示例
定义 Heading.jsx 如下:
- import { defineComponent, h } from "vue";
- export default defineComponent({
- props: {
- level: {
- type: Number,
- required: true,
- },
- },
- setup(props, { slots }) {
- return () =>
- h(
- "h" + props.level, // 标签名
- {}, // prop 或 attribute
- slots.default() // 子节点
- );
- },
- });
复制代码 在 about.vue 中使用,如下:
- <template>
- <Heading :level="1">hello xy</Heading>
- </template>
- <script lang="ts" setup>
- import Heading from "@/components/Heading.jsx";
- </script>
复制代码 当 level 传 1时,效果如下:
当 level 传 6 时,效果如下:
通过 pnpm add @vitejs/plugin-vue-jsx -D 可安装 jsx 插件。
在 vite.config.ts 中配置如下:
- import vueJsx from '@vitejs/plugin-vue-jsx';
- export default defineConfig({
- plugins: [vue(), viteMockServe(), vueJsx()],
- });
复制代码 修改 Heading.jsx 如下:
- import { defineComponent, h } from "vue";
- export default defineComponent({
- props: {
- level: {
- type: Number,
- required: true,
- },
- },
- setup(props, { slots }) {
- const tag = "h" + props.level;
- return () => <tag>{slots.default()}</tag>;
- // return () =>
- // h(
- // "h" + props.level, // 标签名
- // {}, // prop 或 attribute
- // slots.default() // 子节点
- // );
- },
- });
复制代码 todo.jsx 示例
todo.jsx 如下:
- import { defineComponent, ref } from "vue";
- export default defineComponent({
- setup(props) {
- let title = ref("");
- let todos = ref([
- { title: "pc", done: true },
- { title: "android", done: false },
- ]);
- function addTodo() {
- todos.value.push({ title: title.value });
- title.value = "";
- }
- return () => (
- <div>
- <input type="text" vModel={title.value} />
- <button onClick={addTodo}>click</button>
- <ul>
- {todos.value.length ? (
- todos.value.map((todo) => {
- return <li>{todo.title}</li>;
- })
- ) : (
- <li>no data</li>
- )}
- </ul>
- </div>
- );
- },
- });
复制代码 效果如下:
三、AntdVue 组件库使用
业务开辟,重要就是用UI组件库(如antd vue)了,可以在 github 搜项目,例如vue3-antd-admin 和 vue-antd-admin,学习别人的组织思路。
3.1 弹窗
弹窗通常用 a-model 组件
父组件:
- <script setup lang="ts">
- import { ref } from 'vue';
- const visible = ref(false);
- <template>
- <div>
- <a-button type="primary" class="btn-upload" ghost @click="handleCreate">
- <template #icon>
- <svg-icon class="icon" icon="icon-upload" />
- </template>
- 打开弹窗
- </a-button>
- <!-- 子组件。向子组件传参为 model,接收子组件的 @save 事件 -->
- <form-user
- v-model:visible="visible"
- :model="currentProcedure"
- @save="handleSave"
- />
- </div>
- </template>
复制代码 子组件:
- <script setup lang="ts">
- import { User } from '@/models/user';
- import { antdModal } from '@/utils/antd';
- import { reactive, ref, watch } from 'vue';
- import rules from './rules';
- // 数据
- export type FormStateModel = User;
- // 父组件传来的 prop
- const props = defineProps<{
- model?: FormStateModel;
- visible: boolean;
- }>();
- // 向父组件发送的 emit
- const emits = defineEmits<{
- (e: 'save', model: FormStateModel): void;
- (e: 'update:visible', visible: boolean): void;
- }>();
- // 将数据变为响应式
- const formState = reactive<FormStateModel>({
- userName: '',
- password: '',
- });
- // 用于二次确认的业务逻辑。将数据转为JSON字符串。当点击“取消“按钮时,将“现在的数据“和“刚打开弹窗时的数据”做对比,若有变动则需二次确认
- let initModelJSON = '';
- // 当 props.visible 时,调用 init 函数
- watch(() => props.visible, init);
- function init() {
- // 组件初始化时,得到 initModelJSON 的原始值
- const model = props.model;
- if (!model) { // 若为“新增”业务,则父组件未传入 props.model,则先手动赋值为 ‘’ 空字符串做保护,再序列化
- (formState as User).userName = '';
- formState.password = '';
- initModelJSON = JSON.stringify(formState);
- return;
- }
- initModelJSON = JSON.stringify(formState); // 若为“编辑”业务,则父组件传入了 props.model,直接序列化即可
- }
- // 组件的引用:下文的 <a-form ref="formRef"/>
- const formRef = ref();
- async function handleOk() {
- try {
- await formRef.value.validate(); // a-form 官方提供了 validate() 方法
- emits('save', normalizeModel()); // 向父组件emit save函数,参数为 normalizeModel()
- } catch (e) {
- console.error(e);
- }
- }
- async function handleCancel() {
- if (
- // 点击取消按钮时,对比新旧值
- JSON.stringify(formState) !== initModelJSON &&
- !(await antdModal('尚未保存,确定取消?'))
- ) {
- emits('update:visible', true); // 当点击取消时, 向父组件 emit visible = true,让子组件(即本弹窗组件)可见
- return;
- }
- emits('update:visible', false); // 当点击取消时, 向父组件 emit visible = false,让子组件(即本弹窗组件)不可见
- }
- // 将 normalizeModel 暴露给父组件,因为本组件在 <script setup> 中的变量默认是不被父组件可见的
- defineExpose({ normalizeModel });
- function normalizeModel() {
- return { ...formState };
- }
- </script>
- <template>
- <!-- v-bind="$attrs" 将调用组件时的组件标签上绑定的非props的特性(class和style除外)向下传递。在子组件中应当添加inheritAttrs: false(避免父作用域的不被认作props的特性绑定应用在子组件的根元素上)。-->
- <a-modal
- v-bind="$attrs"
- :title="(formState as Procedure).id ? '编辑用户' : '添加用户'"
- :visible="visible"
- @ok="handleOk"
- @cancel="handleCancel"
- >
- <a-form ref="formRef" class="form-device" :rules="rules" :model="formState">
- <a-form-item label="用户账号" class="form-item" name="name">
- <a-input v-model:value="formState.name" placeholder="请输入名称" />
- </a-form-item>
- </a-form>
- </a-modal>
- </template>
- <style lang="scss" scoped>
- @import '~/css/variables';
- </style>
复制代码
- 资料
- Vue 原理视频教程
- 入门项目实战源码
- 完整网站vue3+vite+antd-vue,用于模仿
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |