Vue3 常用知识
vue 3.x
(科普)简述–前端
MVC 模式
即 Model(模型),View(视图) 和 Controller(控制器)
技术:jQuery + Bootstrap 一把梭
MVVM 模式
即 Model(模型),View(视图) 和 ViewModel(视图模型)
标志:AngularJS 的诞生
特点:数据驱动页面(数据发生变化后,通知页面更新)。各大框架在这个步骤上,各显神通:
- Angular 1 就是最老套的脏检查
脏检查:对数据变化的检查上,遵循每次用户交互时都检查一次数据是否变化,有变化就去更新 DOM
- React 1 使用了虚拟 DOM
虚拟 DOM:就像是数据和实际 DOM 的一个缓存层。数据有变化的时候,我们生成一份新的虚拟 DOM 数据,然后再对之前的虚拟 DOM 进行计算,算出需要修改的 DOM(计算逻辑:Diff),再去页面进行操作
缺点:如果虚拟 DOM 树过于庞大,使得计算时间大于 16.6ms,那么就可能会造成性能的卡顿
- Vue 1 使用了响应式
响应式:初始化的时候,Watcher 监听了数据的每个属性,这样数据发生变化的时候,我们就能精确地知道数据的哪个 key 变了,去针对性修改对应的 DOM 即可
缺点:Watcher 监听,本身就比较损耗性能,导致内存占用过多
- Angular 2 引入了 TypeScript、RxJS 等新内容,但是不支持向前兼容
设计很优秀,但是抛弃了老用户,这也是 Angular 这个优秀的框架现在在国内没有大面积推广的原因
- React 2 借鉴了操作系统时间分片的概念,引入了 Fiber 架构,巧妙地利用空闲实现计算,解决了卡顿的问题
把整个虚拟 DOM 树微观化,变成链表,然后我们利用浏览器的空闲时间计算 Diff。一旦浏览器有需求,我们可以把没计算完的任务放在一旁,把主进程控制权还给浏览器,等待浏览器下次空闲
- Vue 2 引入虚拟 DOM
组件之间的变化,可以通过响应式来通知更新;组件内部的数据变化,则通过虚拟 DOM 去更新页面。这样就把响应式的监听器,控制在了组件级别,而虚拟 DOM 的量级,也控制在了组件的大小
版本原理
vue 2
特性
Options API
缺点:
- 由于所有数据都挂载在 this 之上,因而 Options API 的写法对 TypeScript 的类型推导很不友好,并且这样也不好做 Tree-shaking 清理代码。
- 新增功能基本都得修改 data、method 等配置,并且代码上 300 行之后,会经常上下反复横跳,开发很痛苦。
- 代码不好复用,Vue 2 的组件很难抽离通用逻辑,只能使用 mixin,还会带来命名冲突的问题。
<div id="app">
<h1 @click="add">{{count}} * 2 = {{double}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
let App = {
data() {
return {
count: 1,
}
},
methods: {
add() {
this.count++
},
},
computed: {
double() {
return this.count * 2
},
},
}
Vue.createApp(App).mount('#app')
</script>
vue 3
特性
Composition API,即组合 API
缺点
vue 3 响应式系统用了 Proxy,会存在兼容性问题,即不兼容 IE11
优点
- 所有 API 都是 import 引入的,对 Tree-shaking 很友好,没用到的功能,打包的时候会被清理掉 ,减小包的大小。
- 不再上下反复横跳,我们可以把一个功能模块的 methods、data 都放在一起书写,维护更轻松。
- 代码方便复用,可以把一个功能所有的 methods、data 封装在一个独立的函数里,复用代码非常容易。
- Composotion API 新增的 return 等语句,在实际项目中使用
Vue 中用过三种响应式解决方案,分别是 defineProperty API、Proxy 和 value setter。
<div id="app">
<h1 @click="add">{{state.count}} * 2 = {{double}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const { reactive, computed } = Vue
let App = {
setup() {
const state = reactive({
count: 1,
})
function add() {
state.count++
}
const double = computed(() => state.count * 2)
return { state, add, double }
},
}
Vue.createApp(App).mount('#app')
</script>
新的组件
- Fragment: Vue 3 组件不再要求有一个唯一的根节点,清除了很多无用的占位 div。
- Teleport: 允许组件渲染在别的元素内,主要开发弹窗组件的时候特别有用。
- Suspense: 异步组件,更方便开发有异步请求的组件。
新的工程化工具
Vite,它不在 Vue 3 的代码包内,和 Vue 也不是强绑定。Vite 的竞品是 Webpack,而且按照现在的趋势看,使用率超过 Webpack 是迟早的事。
Webpack
等工程化工具的原理,是根据你的 import 依赖逻辑,形成一个依赖图,然后调用对应的处理工具,把整个项目打包后,放在内存里再启动调试。由于要预打包,所以复杂项目的开发,启动调试环境需要 3 分钟都很常见。
Webpack 要把所有路由的依赖打包后,才能开始调试。
- 现代浏览器已经默认支持了 ES6 的 import 语法,
Vite
就是基于这个原理来实现的。即:在调试环境下,不需要全部预打包,只是把你首页依赖的文件,依次通过网络请求去获取,整个开发体验得到巨大提升,做到了复杂项目的秒级调试和热更新。
Vite 一开始就可以准备联调,然后根据首页的依赖模块,再去按需加载,这样启动调试所需要的资源会大大减少。
从零开始
- 下载安装 Node.js 的过程是傻瓜式的,直接去Node.js 官网下载安装即可(可以选择 LTS 版本,也就是稳定版)
在命令行窗口执行 node -v 指令,出现版本号就算安装完成啦
- 对于 Vue 2,官方推荐用 Vue-cli 创建项目;而对于 Vue 3,建议使用 Vite 创建项目,因为 vite 能够提供更好更快的调试体验
命令:
npm init vite
项目骨架:
.
├── .vscode
│ └── extensions.json
├── public 【资源文件】
│ └── vite.svg
├── src 【源码】
│ ├── assets
│ │ └── vue.svg
│ ├── components
│ │ └── HelloWorld.vue
│ ├── App.vue 【 单文件组件】
│ └── main.js 【入口】
│ └── style.css
├── .gitignore
├── index.html 【入口文件】
├── package.json 【管理项目依赖和配置】
├── README.md
├── vite.config.js 【vite工程化配置文件】
- 在 first-project 文件夹内执行
npm install
命令,来进行依赖的安装;然后执行npm run dev
命令来启动项目
在 Chrome 里打开 http://127.0.0.1:5173/ 有页面显示
安装 Vuex 和 vue-router
Vuex 负责管理数据,安装命令:
npm install vue-router@4
vue-router 负责管理路由,安装命令:npm install vuex@next --save
- 规范组织结构
- src 目录的组织结构:
├── src
│ ├── api 【数据请求】
│ ├── assets 【静态资源】
│ ├── components 【组件】
│ ├── pages 【页面】
│ ├── router 【路由配置】
│ ├── store 【vuex数据】
│ └── utils 【工具函数】
- 进入到 router 文件夹中,新建 index.js,写入下面的代码:
import {
createRouter, // createRouter 函数,用来新建路由实例
createWebHashHistory, // createWebHashHistory 函数,用来配置我们内部使用 hash 模式的路由
} from 'vue-router'
import Home from '../pages/home.vue'
import About from '../pages/about.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
- 在 pages 下面新建两个文件 about.vue 和 home.vue,分别输入如下内容:
<template>
<h1>这是about页面</h1>
</template>
<template>
<h1>这是home页面</h1>
</template>
- 在 main.js 中,加载 router 的配置:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
createApp(App).use(router).mount('#app')
- 在 App.vue 中,修改:
<template>
<div>
<router-link to="/">首页</router-link> |
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
</template>
- 至此,项目的基本骨架已经完成。
css 样式
1. 局部样式scoped
<style scoped>
,使 CSS 只会应用到当前组件的元素上,这样就很好地避免了一些样式冲突的问题。
<style scoped>
h1 {
color: red;
}
</style>>
组件就会解析成下面代码的样子:
<h1 data-v-3de47834="">1</h1>
<style scoped>
h1[data-v-3de47834] {
color: red;
}
</style>
2. scoped 内部的全局样式
如果在 scoped 内部,还想写全局的样式,那么可以用 :global
来标记
3. 在 CSS 中使用 JavaScript 中的变量
<template>
<div>
<h1 @click="add">{{ count }}</h1>
</div>
</template>
<script setup>
import { ref } from "vue";
let count = ref(1)
let color = ref('red')
function add() {
count.value++
color.value = Math.random()>0.5? "blue":"red"
}
</script>
<style scoped>
h1 {
color:v-bind(color);
}
</style>>
ref 基础
<template>
<div>
<h1 @click="add">{{count}}</h1>
</div>
</template>
<script setup>
import { ref } from "vue";
let count = ref(1)
function add(){
count.value++
}
</script>
<style>
h1 { color: red; }
</style>
- 对于
ref
返回的响应式数据,我们需要修改.value
才能生效- 在
<script setup>
标签内定义的变量和函数,都可以在模板中直接使用
复用
<template>
<h1>这是首页</h1>
<TodoList />
</template>
<script setup>
import TodoList from '../components/TodoList.vue'
</script>
计算属性
<template>
<div>
<input type="text" v-model="title" @keydown.enter="addTodo" />
<button v-if="active < all" @click="clear">清理</button>
<ul v-if="todos.length">
<li v-for="todo in todos">
<input type="checkbox" v-model="todo.done" />
<span :class="{ done: todo.done }"> {{ todo.title }}</span>
</li>
</ul>
<div v-else>暂无数据</div>
<div>
全选<input type="checkbox" v-model="allDone" />
<span> {{ active }} / {{ all }} </span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
let title = ref("");
let todos = ref([{ title: "学习Vue", done: false }]);
function addTodo() {
todos.value.push({ title: title.value, done: false });
title.value = "";
}
function clear() {
todos.value = todos.value.filter((v) => !v.done);
}
let active = computed(() => {
return todos.value.filter((v) => !v.done).length;
});
let all = computed(() => todos.value.length);
let allDone = computed({
get: function () {
return active.value === 0;
},
set: function (value) {
todos.value.forEach((todo) => {
todo.done = value;
});
},
});
</script>
计算属性 computed 需要单独引入使用,而不是依赖 this 上下文
函数封装
把功能独立的模块封装成一个独立的函数,真正做到按需拆分
<template>
<div>
<input type="text" v-model="title" @keydown.enter="addTodo" />
<button v-if="active < all" @click="clear">清理</button>
<ul v-if="todos.length">
<li v-for="todo in todos">
<input type="checkbox" v-model="todo.done" />
<span :class="{ done: todo.done }"> {{ todo.title }}</span>
</li>
</ul>
<div v-else>暂无数据</div>
<div>
全选<input type="checkbox" v-model="allDone" />
<span> {{ active }} / {{ all }} </span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
let count = ref(1);
function add() {
count.value++;
}
let { title, todos, addTodo, clear, active, all, allDone } = useTodos();
// 和清单相关的所有数据和方法
function useTodos() {
let title = ref("");
let todos = ref([{ title: "学习Vue", done: false }]);
function addTodo() {
todos.value.push({
title: title.value,
done: false,
});
title.value = "";
}
function clear() {
todos.value = todos.value.filter((v) => !v.done);
}
let active = computed(() => {
return todos.value.filter((v) => !v.done).length;
});
let all = computed(() => todos.value.length);
let allDone = computed({
get: function () {
return active.value === 0;
},
set: function (value) {
todos.value.forEach((todo) => {
todo.done = value;
});
},
});
return { title, todos, addTodo, clear, active, all, allDone };
}
</script>
抽离
把组件内部的任何一段代码,从组件文件里抽离出一个独立的文件进行维护
功能:显示鼠标的坐标位置
import {ref, onMounted,onUnmounted} from 'vue'
export function useMouse(){
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
- 组件加载的时候,会触发 onMounted 生命周期,我们执行监听 mousemove 事件,从而去更新鼠标位置的 x 和 y 的值
- 组件卸载的时候,会触发 onUnmounted 生命周期,解除 mousemove 事件
<template>
<div>
<h2>x:{{ x }},y:{{ y }}</h2>
</div>
</template>
<script setup>
import { useMouse } from "../utils/mouse";
let { x, y } = useMouse();
</script>
抽离----本地化
问题:解决所有的操作状态在刷新后就都没了这个问题
思路:让操作状态和本地存储能够同步
操作:显式地声明同步的逻辑,而 watchEffect 这个函数让我们在数据变化之后可以执行指定的函数
import { ref, watchEffect, computed } from "vue";
let title = ref("");
let todos = ref(JSON.parse(localStorage.getItem('todos')||'[]'));
watchEffect(()=>{
localStorage.setItem('todos',JSON.stringify(todos.value))
})
function addTodo() {
todos.value.push({
title: title.value,
done: false,
});
title.value = "";
}
升级版:把 useStorage 这个函数可以抽离成一个文件,放在工具函数文件夹中
function useStorage(name, value=[]){
let data = ref(JSON.parse(localStorage.getItem(name)|| value))
watchEffect(()=>{
localStorage.setItem(name,JSON.stringify(data.value))
})
return data
}
使用时,把 ref 变成 useStorage
let todos = useStorage('todos',[])
function addTodo() {
...code
}
抽离----替换 favicon 图标
把对图标的对应修改的操作封装成了 useFavicon 函数,并且通过 ref 和 watch 的包裹,我们还把小图标变成了响应式数据
import {ref,watch} from 'vue'
export default function useFavicon( newIcon ) {
const favicon = ref(newIcon)
const updateIcon = (icon) => {
document.head
.querySelectorAll(`link[rel*="icon"]`)
.forEach(el => el.href = `${icon}`)
}
const reset = ()=>favicon.value = '/favicon.ico'
watch( favicon,
(i) => {
updateIcon(i)
}
)
return {favicon,reset}
}
通过响应式的方式去修改和使用小图标,通过对 faivcon.value 的修改就可以随时更换网站小图标。下面的代码,就实现了在点击按钮之后,修改了网页的图标为 girl.png 的操作
<script setup>
import useFavicon from './utils/favicon'
let {favicon} = useFavicon()
function loading(){
favicon.value = '/girl.png'
}
</script>
<template>
<button @click="loading">123</button>
</template>
VueUse 插件
Composition API 的工具集合,适用于 Vue 2.x 或者 Vue 3.x
安装: npm install @vueuse/core
使用 VueUse 的 useFullscreen 来返回全屏的状态和切换全屏的函数:
<template>
<h1 @click="toggle">click</h1>
</template>
<script setup>
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen()
</script>
一些函数
- defineProps
作用:规范传递数据的格式
<template>
<div>
{{rate}}
</div>
</template>
<script setup>
import { defineProps,computed } from 'vue';
let props = defineProps({
value: Number,
theme: { type: String, default: "orange" },
});
let rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
const themeObj = {
black: "#00",
white: "#fff",
red: "#f5222d",
orange: "#fa541c",
yellow: "#fadb14",
green: "#73d13d",
blue: "#40a9ff",
};
const fontstyle = computed(() => {
return `color:${themeObj[props.theme]};`;
});
</script>
- defineEmits
接收父组件传递过来的方法
父子传值
使用 defineEmit 向父元素传递数据
<template>
<span @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</template>
<script setup>
import { defineProps, defineEmits,computed, ref} from 'vue';
let emits = defineEmits('update-rate')
function onRate(num){
emits('update-rate',num)
}
</script>
用 @update-rate 接收组件 emit 的数据,并且修改 score 的值
<template>
<h1>你的评分是 {{score}}</h1>
<Rate :value="score" @update-rate="update"></Rate>
</template>
<script setup>
import {ref} from 'vue'
import Rate from './components/Rate1.vue'
let score = ref(3.5)
function update(num){
score.value = num
}
</script>
Vuex
Vuex 由于在 API 的设计上,对 TypeScript 的类型推导的支持比较复杂,用起来很是痛苦。
为了解决这个问题,Vuex 的作者最近发布了一个新的作品叫 Pinia,并将其称之为下一代的 Vuex。
Pinia 不需要 Vuex 自定义复杂的类型去支持 TypeScript,天生对类型推断就非常友好,并且对 Vue Devtool 的支持也非常好。
骨架分析
- state 中定义数据
- mutation 中修改数据
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count: 666
}
},
mutations: {
add (state) {
state.count++
}
},
getters:{
double(state){
return state.count*2
}
},
actions:{
asyncAdd({commit}){
setTimeout(()=>{
commit('add')
},1000)
}
}
})
export default store
function asyncAdd(){
store.dispatch('asyncAdd')
}
- getters 类似计算属性,对 state 中数据行进扩展
- actions 中做任意的异步处理。可以通过解构获得 commit 函数来执行 mutations 去更新数据。
action 并不是直接修改数据,而是通过 mutations 去修改
h 函数
h 函数内部是调用 createVnode 来返回虚拟 DOM。即,创建虚拟 DOM 的函数。
更方便的写 h 函数的方式 ---- JSX
使用 JSX 的本质,还是在写 JavaScript
在 JavaScript 里面写 HTML 的语法,就叫做 JSX
从 JSX 到 createVNode 函数的转化过程中,我们需要安装一个 JSX 插件:
npm install @vitejs/plugin-vue-jsx -D
配置 vite.config.js:
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx';
export default defineConfig({
plugins: [vue(),vueJsx()]
})
模块化
1. 默认导出、导入
- 导出语法:export default 默认导出的成员
let n = 10 // 模块私有成员
function show(){} // 模块私有方法
export default { n, show }
- 导入语法:import 接收名称 from ‘模块标识符’
// 从 01_m1.js 模块中导入 export default 向外共享的成员:
import m1 from './01_m1.js'
console.log(m1)
// 打印输出为 { n: 10, show: [Function: show] }
注意事项:
- 导入时的接收名称可以自定义
- 每个模块中只能存在一个 export default,多了会报错:
export default { n, show } export default { n2 } // 会报错
2. 按需导出、导入
导出语法:export 按需导出的成员
export let n = 10
export function show(){}
导入语法:import { 导出成员的名称 } from ‘模块标识符’
import { n, show } from './01_m1.js'
console.log(n)
// 打印输入 10
console.log(show)
// 打印输出 [Function: show]
注意事项:
- 按需导入的成员名称必须和按需导出的名称一致
- 按需导入时可以使用 as 关键字进行重命名
- 按需导入可以和默认导入一起使用
3. 直接导入并执行模块中的代码
如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码:
// 导入该模块时,单纯执行下 for 循环操作
for (let i = 0; i < 3; i++) {
console.log(i)
}
import './01_m1.js'
TypeScript
类型限制
let boyName:string = '小明'
let age:number = 18
let isMan:boolean = false
let boySales:undefined
let timer:null = null
let girl:[string,number] = ['小红',18]
枚举 enum
enum girlFriends {'小三','小红','小花'} // 枚举类型
let scores = [girlFriends['小三'], girlFriends['小红'], girlFriends['小花']]
接口 interface
定义对象的类型限制
interface person {
name:string,
price:number[], // 类型为数字组成的数组
friend?:string|boolean, // string 或者 boolean,并且通过 ? 设置为可选属性
readonly age:number // readonly 为只读
}
let vueCourse:person = {
name:'小三',
price:1800,
friend:false
age:18
}
泛型 T
指有些函数的参数,不确定要定义成什么类型,而返回值类型需要根据参数来确定。
function identity0(arg: any): any {
return arg
}
// 相当于type T = arg的类型
function identity<T>(arg: T): T {
return arg
}
identity<string>('玩转vue 3全家桶') // 这个T就是string,所以返回值必须得是string
identity<number>(1)
函数类型
语法:function 函数名(参数:参数类型):返回值类型{}
function add(x: number, y: number): number {
return x + y;
}
add(1, 2);
语法:(参数类型) => 返回值类型
特点:使用变量的方式去定义函数
let add1:(a:number,b:number)=>number =
function(x: number, y: number): number {
return x + y;
}
可选择
|
符号
let course:string|number = '哈哈'
type course1 = '一' | '二' | '三'
let course2:course1 = '三'
- keyof 语法
interface VueCourse5 {
name:string,
price:number
}
type CourseProps = keyof VueCourse5 // 只能是name和price选一个
let k:CourseProps = 'name'
let k1:CourseProps = 'p' // 改成price
三元表达式 extends
// T extends U ? X : Y 类型三元表达式
type ExtendsType<T> = T extends boolean ? "重学前端" : "玩转Vue 3"
type ExtendsType1 = ExtendsType<boolean> // type ExtendsType1='重学前端'
type ExtendsType2 = ExtendsType<string> // type ExtendsType2='玩转Vue 3'
遍历 in
通过 k in Courses 语法,相当于遍历了 Courses 所有的类型作为 CourseObj 的属性,值的类型是 number
type Courses = '玩转Vue 3'|'重学前端'
type CourseObj = {
[k in Courses]:number // 遍历Courses类型作为key
}
// 上面的代码等于下面的定义
// type CourseObj = {
// 玩转Vue 3: number;
// 重学前端: number;
// }
vue 3 中使用 echarts
下载 echarts:
npm install echarts --save
使用 echarts:
<template>
<div ref="chartDom"></div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import * as echarts from "echarts"; // 按需引入 echarts
const chartDom = ref() // 使用 ref 创建虚拟 DOM 引用,使用时用 chartDom.value
onMounted(() => {
init()
})
function init() {
const myChart = echarts.init(chartDom.value); // 基于准备好的 DOM,初始化 echarts 实例
const option = { // 指定图表的配置项和数据
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
};
myChart.setOption(option); // 使用刚指定的配置项和数据显示图表
}
</script>
若要动态渲染,只需将动态数据替换掉 data 即可。
特别注意:数据的获取时机很重要,有的时候数据还没获取到 echarts 就已经渲染好了,导致数据不能在 echarts 上正常展示。建议将要渲染的数据存储到 Pinia 或本地存储,在渲染 echarts 之前就将数据给到 echarts,保证万无一失。
vue 3 中使用反向代理
我使用 vite 搭建的 vue3.X 项目,使用 ts 开发,项目骨架如下:
在 src/utils/request.ts 文件中封装请求:
// request.ts
import axios, { AxiosError } from "axios";
import type { AxiosInstance, AxiosResponse, AxiosRequestConfig } from "axios";
const service: AxiosInstance = axios.create({
baseURL: "/api", // 接口添加 /api 前缀。如接口 /user/get 会映射成 /api/user/get
timeout: 5000,
});
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
return config;
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
if (response.status === 200) {
return response;
} else {
Promise.reject();
}
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
export default service;
在项目根目录的 vite.config.ts 文件中写 代理:
// vite.config.ts
import { defineConfig } from "vite";
// ...
export default defineConfig({
// ...
server: {
proxy: {
"/api": {
// 如:请求 http://127.0.0.1:5173/api/user/get 会被映射到 http://127.0.0.1:3000/user/get
target: "http://127.0.0.1:3000/",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
})