Vue - 核心语法
创建
创建vue3
npm create vue@latest
✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add an End-to-End Testing Solution? … No / Cypress / Nightwatch / Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Vue DevTools 7 extension for debugging? (experimental) … No / Yes
Scaffolding project in ./<your-project-name>...
Done.
创建vue2
安装vue-cli
npm install -g @vue/cli
创建
vue create vue2_lifecycle

插件
Vue (Official)
Vue VSCode Snippets
根组件
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。
挂载应用
应用实例必须在调用了 .mount() 方法后才会渲染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串:
createApp(App).mount('#app')
应用根组件的内容将会被渲染在容器元素里面。容器元素自己将不会被视为应用的一部分。
.mount() 方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。
应用配置
应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项,例如定义一个应用级的错误处理器,用来捕获所有子组件上的错误:
app.config.errorHandler = (err) => {
/* 处理错误 */
}
应用实例还提供了一些方法来注册应用范围内可用的资源,例如注册一个组件:
app.component('TodoDeleteButton', TodoDeleteButton)
这使得 TodoDeleteButton 在应用的任何地方都是可用的。我们会在指南的后续章节中讨论关于组件和其他资源的注册。你也可以在 API 参考中浏览应用实例 API 的完整列表。
确保在挂载应用实例之前完成所有应用配置!
先来一个简单的例子
<template>
<div>
<h2>Name: {{ name }}</h2>
<p>Age: {{ age }}</p>
<button @click="changeName">修改</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 响应式状态
let name = ref('张三')
let age = ref(18)
let tel = ref('13800000000')
function showTel() {
console.log(tel.value)
}
function changeName() {
name.value = '李四'
}
</script>
核心语法
1 拉开序幕的setup
setup() 钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
- 需要在非单文件组件中使用组合式 API 时。
- 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
对于结合单文件组件使用的组合式 API,推荐通过
<script setup>以获得更加简洁及符合人体工程学的语法。
<template>
<div>
<h2>Name: {{ name }}</h2>
<p>Age: {{ age }}</p>
<button @click="changeName">修改</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>
<script>
// 响应式状态
let name = '张三'
let age = 18
let tel = '13800000000'
export default {
setup() {
// `setup()` 中访问 `this` 会是 `undefined`,vue3已经弱化this
function showTel() {
console.log(tel)
}
function changeName() {
name = '李四'//这样修改页面是没有变化的
console.log(name) //name 已经改了,但不是响应式
}
return { name, age, tel, showTel, changeName }
}
}
</script>
setup() 自身并不含对组件实例的访问权,即在 setup() 中访问 this 会是 undefined。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。
setup 的语法糖
直接在script 添加setup
<script setup>
// 响应式状态
let name = '张三'
let age = 19
let tel = '13800000000'
function showTel() {
console.log(tel)
}
function changeName() {
name = '李四'//这样修改页面是没有变化的
console.log(name) //name 已经改了,但不是响应式
}
</script>
这个时候如果组件命名的话有2种方法:

一、分开的script
<script>
export default {
name: 'Person223',
}
</script>
<script setup>
// 响应式状态
let name = '张三'
let age = 19
let tel = '13800000000'
function showTel() {
console.log(tel)
}
function changeName() {
name = '李四'//这样修改页面是没有变化的
console.log(name) //name 已经改了,但不是响应式
}
</script>
二、插件 vite-plugin-vue-setup-extend
安装
npm i vite-plugin-vue-setup-extend -d
vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueDevTools from "vite-plugin-vue-devtools";
import vueSetupExtend from "vite-plugin-vue-setup-extend";
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
vueSetupExtend()
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});src\components\Person.vue
<!--用了vite-plugin-vue-setup-extend,这里可以使用name配置名字-->
<script setup name="Person333">
// 响应式状态
let name = '张三'
let age = 19
let tel = '13800000000'
function showTel() {
console.log(tel)
}
function changeName() {
name = '李四'//这样修改页面是没有变化的
console.log(name) //name 已经改了,但不是响应式
}
</script>
2 响应式
ref() 基本类型
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
作用:定义响应式变量。
语法:let xxx=ref(初始值)。
返回值:一个RefImpl的实例对象,简称ref对象或ref,ref对象的value属性是响应式的。
注意点:
JS中操作数据需要:xxx.value,但模板中不需要要.value,直接使用即可。
对于let name=ref(‘张三')来说,name不是响应式的,name.value是响应式的。
<template>
<div>
<h2>Name: {{ name }}</h2>
<p>Age: {{ age }}</p>
<button @click="changeName">修改</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>
<script setup name="Person">
import { ref } from "vue"
// 响应式状态
let name = ref('张三') // 响应式数据
let age = ref(19) // 响应式数据
let tel = '13800000000'
function showTel() {
console.log(tel)
}
function changeName() {
name.value = '李四' // 响应式数据
console.log(name.value)
}
</script>reactive() 只能定义对象类型
响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。
值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。
若要避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,请使用 shallowReactive() 作替代。
返回的对象以及其中嵌套的对象都会通过 ES Proxy 包裹,因此不等于源对象,建议只使用响应式代理,避免使用原始对象。
Car.vue
<template>
<div>
<h2>一辆车的名称: {{ car.name }}</h2>
<p>Price: {{ car.price }}</p>
<button @click="changePrice">修改价格</button>
</div>
</template>
<script setup>
import { reactive } from "vue"
let carInfo = {
name: '奔驰',
price: 1000000
}
console.log(carInfo) // {name: '奔驰', price: 1000000}
let car = reactive(carInfo)
console.log(car) //Proxy(Object) {name: '奔驰', price: 1000000}
function changePrice() {
car.price += 10000 //这里不用value,因为reactive已经将carInfo转换为响应式对象
}
</script>

ref 也可以定义对象,但是实际上对象里面还是调用的 reactive,所以对象最好直接使用 reactive
<script setup name="Person"> import { ref } from "vue" let carInfo = { name: '奔驰', price: 1000000 } console.log(carInfo) let car = ref(carInfo) console.log(car) function changePrice() { car.value.price += 10000; } </script>
ref 与 reactive 对比
宏观角度看:
1.ref用来定义:基本类型数据、对象类型数据;
2.reactive用来定义:对象类型数据。
区别:
1.ref创建的变量必须使用.value(可以使用volar插件自动添加.value,当前插件名字是 Vue (Official))。


2.reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。
<template>
<div>
<h2>一辆车的名称: {{ car.name }}</h2>
<p>Price: {{ car.price }}</p>
<button @click="changePrice">修改价格</button>
<button @click="changeCar">修改整车</button>
</div>
</template>
<script setup>
import { reactive } from "vue"
let carInfo = {
name: '奔驰',
price: 1000000
}
let car = reactive(carInfo)
function changePrice() {
car.price += 10000;
}
function changeCar() {
//失败1
// car = {
// name: '保时捷',
// price: 2000000
// };
//失败2
// car = reactive({
// name: '保时捷',
// price: 2000000
// });
//成功
Object.assign(car, { name: '保时捷', price: 2000000 })
}
</script>使用原则:
1.若需要一个基本类型的响应式数据,必须使用ref。
2.若需要一个响应式对象,层级不深,ref、reactive都可以。
3.若需要一个响应式对象,且层级较深,推荐使用reactive。
toRefs()
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
<template>
<div>
<h2>Name: {{ name }}</h2>
<p>Age: {{ age }}</p>
<button @click="changeName">修改</button>
</div>
</template>
<script setup>
import { reactive } from "vue"
let person = reactive({
name: '张三',
age: 18
})
let { name, age } = person
console.log(name)
function changeName() {
name += '~'
console.log('name:' + name, "person.name:" + person.name)
}
</script>
这个时候,页面是没有变化的。这里的name打印出来也只是一个string的张三

如果使用toRefs ,消费者组件可以解构/展开返回的对象而不会失去响应性:
<template>
<div>
<h2>Name: {{ name }}</h2>
<p>Age: {{ age }}</p>
<button @click="changeName">修改</button>
</div>
</template>
<script setup>
import { reactive, toRefs } from "vue"
let person = reactive({
name: '张三',
age: 18
})
let { name, age } = toRefs(person)
console.log(name)
function changeName() {
name.value += '~'
console.log('name:' + name.value, "person.name:" + person.name)
}
</script>这里的name,打印出来是一个ObjectRefImpl。

toRef()
可以将值、refs 或 getters 规范化为 refs (3.3+)。
也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
const state = reactive({
foo: 1,
bar: 2
})
// 双向 ref,会与源属性同步
const fooRef = toRef(state, 'foo')
// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2
// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3
computed() 计算
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
创建一个只读的计算属性 ref:
<template>
<div class="Person">
<span>First Name: <input type="text" v-model="firstname"></span> <br>
<span>Last Name: <input type="text" v-model="lastname"></span> <br>
<span>Full Name: <input type="text" v-model="fullname"></span>
</div>
</template>
<script setup>
import { computed, ref } from "vue"
let firstname = ref('zhang')
let lastname = ref('san')
let fullname = computed(() => {
return firstname.value.slice(0, 1).toUpperCase() + firstname.value.slice(1) + " " + lastname.value;
})
</script>
创建一个可写的计算属性 ref:
<template>
<div class="Person">
<span>First Name: <input type="text" v-model="firstname"></span> <br>
<span>Last Name: <input type="text" v-model="lastname"></span> <br>
<span>Full Name: <input type="text" v-model="fullname"></span>
<span>修改名字: <button @click="changeName">修改</button></span>
</div>
</template>
<script setup>
import { computed, ref } from "vue"
let firstname = ref('zhang')
let lastname = ref('san')
let fullname = computed({
get() {
return firstname.value.slice(0, 1).toUpperCase() + firstname.value.slice(1) + " " + lastname.value;
},
set(value) {
firstname.value = value.split(" ")[0];
lastname.value = value.split(" ")[1];
}
})
function changeName() {
fullname.value = "li si";
}
</script>

调试:
const plusOne = computed(() => count.value + 1, {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
watch() 监听
// 侦听单个来源
function watch<T>(
source: WatchSource<T>,
callback: WatchCallback<T>,
options?: WatchOptions
): WatchHandle
// 侦听多个来源
function watch<T>(
sources: WatchSource<T>[],
callback: WatchCallback<T[]>,
options?: WatchOptions
): WatchHandle
type WatchCallback<T> = (
value: T,
oldValue: T,
onCleanup: (cleanupFn: () => void) => void
) => void
type WatchSource<T> =
| Ref<T> // ref
| (() => T) // getter
| (T extends object ? T : never) // 响应式对象
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // 默认:false
deep?: boolean | number // 默认:false
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
once?: boolean // 默认:false (3.4+)
}
interface WatchHandle {
(): void // 可调用,与 `stop` 相同
pause: () => void
resume: () => void
stop: () => void
}
watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
第一个参数是侦听器的源。这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象
- ...或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
第三个可选的参数是一个对象,支持以下这些选项:
immediate:在侦听器创建时立即触发回调。第一次调用时旧值是undefined。deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。在 3.5+ 中,此参数还可以是指示最大遍历深度的数字。参考深层侦听器。flush:调整回调函数的刷新时机。参考回调的刷新时机及watchEffect()。onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。once:(3.4+) 回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
与 watchEffect() 相比,watch() 使我们可以:
- 懒执行副作用;
- 更加明确是应该由哪个状态触发侦听器重新执行;
- 可以访问所侦听状态的前一个值和当前值。
情况一:监视【ref】定义的基本数据
<template>
<div class="Person">
<h2>情况一:监视【ref】定义的基本数据</h2>
<p>当前点击次数 {{ count }}</p>
<span> <button @click="getSum">点击+1</button></span>
</div>
</template>
<script setup>
import { watch, ref } from "vue"
let count = ref(0)
function getSum() {
count.value++
}
const stop1 = watch(count, (newValue, oldValue) => {
if (newValue > 5) {
stop1()
}
console.log(newValue, oldValue)
})
</script>
停止侦听器:
const stop = watch(source, callback)
// 当已不再需要该侦听器时:
stop()
暂停/恢复侦听器:3.5+
const { stop, pause, resume } = watch(() => {})
// 暂停侦听器
pause()
// 稍后恢复
resume()
// 停止
stop()
情况二:监视【ref】定义的对象数据
监视 ref 定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
<template>
<div class="Person">
<h2>情况二:监视【ref】定义的对象数据</h2>
<p>姓名: {{ person.name }}</p>
<p>年龄: {{ person.age }}</p>
<span> <button @click="changeName">修改姓名</button></span>
<span> <button @click="changePerson">修改人</button></span>
</div>
</template>
<script setup>
import { watch, ref } from "vue"
//#region 情况二:监视【ref】定义的对象数据
let person = ref({
name: "张三",
age: 18
})
function changeName() {
person.value.name += "~"
}
function changePerson() {
person.value = {
name: "王五",
age: 20
}
}
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
//#endregion
</script>
上面代码,点击 changeName 是没有进入watch的,除非加入 deep
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, {
immediate: false, //初始化时执行
deep: true //监听对象内部属性的修改
})

注意:
- 若修改的是
ref定义的对象中的属性,newValue和oldValue都是新值,因为它们是同一个对象。 - 若修改整个
ref定义的对象,newValue是新值,oldValue是旧值,因为不是同一个对象了。
情况三:监视【reactive】定义的对象数据,且默认开启深度监视
<template>
<div class="Person">
<h2>情况三:监视【reactive】定义的对象数据,且默认开启深度监视</h2>
<p>姓名: {{ person.name }}</p>
<p>年龄: {{ person.age }}</p>
<span> <button @click="changeName">修改姓名</button></span>
<span> <button @click="changePerson">修改人</button></span>
</div>
</template>
<script setup>
import { reactive, watch } from "vue"
let person = reactive({
name: "张三",
age: 18
})
function changeName() {
person.name += "~"
}
function changePerson() {
Object.assign(person, {
name: "王五",
age: 20
})
}
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
</script>
情况四:监视 ref 或 reactive 定义的【对象类型】数据中的某个属性(侦听一个 getter 函数)
监视 ref 或 reactive 定义的【对象类型】数据中的某个属性,注意点如下:
- 若该属性值不是【对象类型】,需要写成函数形式。
- 若该属性值依然是【对象类型】,可直接编,也可写成函数,建议写成函数。
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)下面再来个例子:
<template>
<div class="Person">
<h2>情况三:监视【reactive】定义的对象数据,且默认开启深度监视</h2>
<p>姓名: {{ person.name }}</p>
<p>年龄: {{ person.age }}</p>
<p>车1: {{ person.Car.Car1 }}</p>
<p>车2: {{ person.Car.Car2 }}</p>
<span> <button @click="changeCar">修改车1</button></span>
<span> <button @click="changeCar2">修改车2</button></span>
<span> <button @click="changeAllCar">修改整车</button></span>
</div>
</template>
<script setup>
import { reactive, watch } from "vue"
let person = reactive({
name: "张三",
age: 18,
Car: {
Car1: "奔驰",
Car2: "宝马"
}
})
//对象属性换掉,调用 watch(person.Car,...
function changeCar() {
person.Car.Car1 = "兰博基尼"
}
//对象属性换掉,调用 watch(person.Car,...
function changeCar2() {
person.Car.Car2 = "路虎"
}
//整个对象换掉,调用 watch(() => person.Car,...
function changeAllCar() {
person.Car = { Car1: "法拉利", Car2: "保时捷" }
}
watch(person.Car, (newValue, oldValue) => {
console.log("直接写对象:", newValue, oldValue)
})
watch(() => person.Car, (newValue, oldValue) => {
console.log("对象函数:", newValue, oldValue)
})
</script>
优化使用方法:
//既监视对象,也监视对象属性
watch(() => person.Car, (newValue, oldValue) => {
console.log("对象函数:", newValue, oldValue)
}, { deep: true })
情况五:当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
watchEffect 自动化监视依赖
- 官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
watch对比watchEffect- 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch:要明确指出监视的数据watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
const count = ref(0)
watchEffect(() => console.log("输出:", count.value))
// -> 输出 0
count.value++
// -> 输出 1
页面加载,这里会打印:
输出: 0
输出: 1
标签的 ref Attributes
用于注册模板引用。
两种方式:
方式一:useTemplateRef
方式二:ref
<template>
<div>
<p ref="age2">Age: {{ age + 1 }}</p>
<p ref="age3">Age: {{ age + 2 }}</p>
<button @click="showRef2">查看ref2</button>
<button @click="showRef3">查看ref3</button>
</div>
</template>
<script setup name="Person">
import { ref, useTemplateRef } from "vue"
let age = ref(19) // 响应式数据
let ref2 = useTemplateRef('age2') //方式一
let age3 = ref() //方式二
function showRef2() {
console.log(ref2.value) //<p>Age: 20</p>
}
function showRef3() {
console.log(age3.value) //<p>Age: 21</p>
}
</script>
如果用于普通 DOM 元素,引用将是元素本身;如果用于子组件,引用将是子组件的实例。
或者
ref可以接收一个函数值,用于对存储引用位置的完全控制:<ChildComponent :ref="(el) => child = el" />关于 ref 注册时机的重要说明:因为 ref 本身是作为渲染函数的结果来创建的,必须等待组件挂载后才能对它进行访问。
Props
一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute (关于透传 attribute,我们会在专门的章节中讨论)。
在使用 <script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明:
vue
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
在没有使用 <script setup> 的组件中,props 可以使用 props 选项来声明:
export default {
props: ['foo'],
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.foo)
}
}
注意传递给 defineProps() 的参数和提供给 props 选项的值是相同的,两种声明方式背后其实使用的都是 props 选项。
除了使用字符串数组来声明 props 外,还可以使用对象的形式:
// 使用 <script setup>
defineProps({
title: String,
likes: Number
})
// 非 <script setup>
export default {
props: {
title: String,
likes: Number
}
}
对于以对象形式声明的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数。比如,如果要求一个 prop 的值是 number 类型,则可使用 Number 构造函数作为其声明的值。
对象形式的 props 声明不仅可以一定程度上作为组件的文档,而且如果其他开发者在使用你的组件时传递了错误的类型,也会在浏览器控制台中抛出警告。我们将在本章节稍后进一步讨论有关 prop 校验的更多细节。
如果你正在搭配 TypeScript 使用 <script setup>,也可以使用类型标注来声明 props:
<script setup lang="ts">
defineProps<{
title?: string
likes?: number
}>()
</script>
例子:
src\types\index.ts
export interface Person {
id: string;
name: string;
age: number;
}
export type PersonList = Person[];
src\App.vue
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Prop from './components/Prop.vue'
// 响应式状态
import { type PersonList } from '@/types';
let persons: PersonList = reactive([
{ name: '张三', age: 18, id: '1' },
{ name: '王五', age: 19, id: '2' }
])
</script>
<template>
<Prop :thePersons="persons"></Prop>
<!-- <Prop></Prop> -->
</template>
src\components\Prop.vue
<template>
<div class="Prop">
<h3>人员列表</h3>
<ul>
<li v-for="person in thePersons" :key="person.id">
{{ person.name }} - {{ person.age }}岁
</li>
</ul>
</div>
</template>
<script setup name="Prop" lang="ts">
import { type PersonList } from '@/types';
import type { PropType } from 'vue';
//1 只接收
// defineProps(["thePersons"]);
//2 接收 + 限制
//2.1
// defineProps<{
// thePersons: PersonList
// }>();
//2.2.1
// defineProps({ thePersons: Array as PropType<PersonList> });
//2.2.2
// defineProps({ thePersons: { type: Array as PropType<PersonList> } });
//3 接收 + 限制类型 + 限制必要性 + 指定默认值
// 3.1
withDefaults(defineProps<{ thePersons?: PersonList }>(),
{ thePersons: () => [{ id: "0", name: "默认姓名", age: 22 }] }
);
// 3.2
// defineProps({
// thePersons: {
// type: Array as PropType<PersonList>,
// required: true,
// default: () => [{ id: 1, name: "啊啊啊", age: 22 }]
// }
// });
</script>
<style scoped>
.Prop {
margin: 20px;
padding: 10px;
border: 1px solid #ccc;
}
</style>
Vue 生命周期
Vue 2 生命周期(选项式 API)
- 初始化阶段
beforeCreate:实例创建前调用,数据观测和事件配置尚未完成created:实例创建完成,已完成数据观测,但未挂载到 DOM
- 挂载阶段
beforeMount:模板编译完成,即将挂载到 DOMmounted:组件已挂载到 DOM,可访问 DOM 元素(常用)
- 更新阶段
beforeUpdate:数据更新时触发,DOM 尚未重新渲染updated:数据更新并完成 DOM 重新渲染后触发
- 销毁阶段
beforeDestroy:实例销毁前调用,仍可访问组件实例destroyed:实例销毁后调用,所有事件监听和子组件已移除
| 生命周期钩子 | 执行时机 | 核心用途(常见场景) |
|---|---|---|
beforeCreate |
实例创建前,数据 / 事件未初始化 | 几乎不用(无访问实例的能力) |
created |
实例创建完成,数据已初始化 | 发起异步请求(如接口调用)、初始化数据 |
beforeMount |
模板编译完成,DOM 未挂载 | 可修改数据(不会触发额外渲染) |
mounted |
DOM 挂载完成 | 操作 DOM(如初始化第三方插件)、绑定事件 |
beforeUpdate |
数据更新,DOM 未重新渲染 | 读取当前 DOM 状态(如获取输入框值) |
updated |
数据更新,DOM 已重新渲染 | 基于新 DOM 执行操作(如滚动定位) |
beforeDestroy |
组件销毁前,实例仍可用 | 清理资源(清除定时器、解绑事件) |
destroyed |
组件销毁完成,实例不可用 | 几乎不用(资源已清理) |
Vue 3 生命周期流程图(组合式 API 为主)
| Vue 3 钩子(组合式) | 对应选项式钩子 | 执行时机 | 核心用途 |
|---|---|---|---|
| - | beforeCreate |
实例创建前,数据 / 事件未初始化 | 几乎不用 |
| - | created |
实例创建完成,数据已初始化 | 发起异步请求(组合式中可直接在 setup 写) |
onBeforeMount |
beforeMount |
模板编译完成,DOM 未挂载 | 修改数据(不触发额外渲染) |
(常用)onMounted |
mounted |
DOM 挂载完成 | 操作 DOM、绑定事件(最常用) |
onBeforeUpdate |
beforeUpdate |
数据更新,DOM 未重新渲染 | 读取当前 DOM 状态 |
(常用)onUpdated |
updated |
数据更新,DOM 已重新渲染 | 基于新 DOM 执行操作 |
(常用)onBeforeUnmount |
beforeUnmount |
组件卸载前,实例仍可用 | 清理资源(清除定时器、解绑事件) |
onUnmounted |
unmounted |
组件卸载完成,实例不可用 | 几乎不用 |
onRenderTracked |
- | 响应式数据被追踪时(开发环境) | 调试响应式依赖(如 “谁触发了渲染”) |
onRenderTriggered |
- | 响应式数据更新触发渲染时(开发) | 调试渲染触发源(如 “哪个数据变了”) |
onMounted()
注册一个回调函数,在组件挂载完成后执行。
<script setup>
import { ref, onMounted } from 'vue'
const el = ref()
onMounted(() => {
el.value // <div>
})
</script>
<template>
<div ref="el"></div>
</template>
注册一个回调函数,在组件挂载完成后执行。
Vue 2 vs Vue 3 生命周期核心区别对比
| 对比维度 | Vue 2 | Vue 3 |
|---|---|---|
| 核心命名差异 | 销毁阶段:beforeDestroy/destroyed |
卸载阶段:beforeUnmount/unmounted(更语义化) |
| API 风格支持 | 仅选项式 API(钩子写在组件选项中) | 1. 选项式 API(兼容 Vue 2 写法) 2. 组合式 API(onXxx 函数,推荐) |
| 新增钩子 | 无 | onRenderTracked/onRenderTriggered(开发调试用) |
| 入口差异 | 无特殊入口 | 组合式 API 需通过 setup() 函数定义钩子(Vue 3.2+ 也支持 <script setup> 语法糖) |
| 执行时机兼容 | - | 除 “销毁→卸载” 命名外,其他钩子执行时机完全对齐 Vue 2,迁移成本低 |
关键总结(避坑指南)
- 最常用钩子:无论 Vue 2 还是 Vue 3,
mounted(Vue 3 为onMounted)和 “卸载 / 销毁” 钩子(onUnmounted/destroyed)是开发中最常用的 —— 前者做初始化,后者做资源清理。 - Vue 3 迁移注意:若从 Vue 2 迁移到 Vue 3,只需将
beforeDestroy改为beforeUnmount,destroyed改为unmounted(选项式 API);若用组合式 API,直接导入onBeforeUnmount/onUnmounted即可。 - 调试钩子:
onRenderTracked和onRenderTriggered仅在开发环境生效,生产环境会被移除,可用于定位 “不必要的渲染” 问题。
Vue Hooks
在 Vue 中,Hooks(钩子函数)是 Composition API 中用于封装和复用逻辑的重要概念,类似于 React Hooks,但有其自身特点。Vue Hooks 允许你将组件逻辑抽离为可重用的函数,使代码结构更清晰、复用性更高。
核心特点:
- 逻辑复用:将组件中的复杂逻辑(如数据请求、表单处理、定时器等)抽离为独立的 Hook 函数,可在多个组件中复用。
- 组合式编程:通过组合多个 Hooks 实现复杂功能,替代了 Options API 中按选项(data、methods 等)划分的方式。
- 与组件生命周期关联:可以在 Hook 中使用
onMounted、onUpdated等生命周期钩子。 - 响应式集成:自然配合
ref、reactive等响应式 API 使用,维护组件状态。
常见内置 Hooks(Vue 3):
Vue 提供了一系列内置 Hooks 用于处理组件生命周期、依赖注入等:
- 生命周期类:
onMounted、onUpdated、onUnmounted、onBeforeMount等 - 依赖注入:
provide、inject - 状态管理:
useAttrs、useSlots、useCssModule - 其他:
watch、watchEffect、computed(严格来说是响应式 API,但常与 Hooks 配合使用)
自定义 Hooks
最常用的场景是创建自定义 Hooks,命名通常以 use 开头(如 useFetch、useForm)。
示例:创建一个处理数据请求的自定义 Hook
// hooks/useFetch.js
import { ref, onMounted } from "vue";
export default function (url: string) {
const data = ref(null);
const loading = ref(true);
const error = ref(null);
const fetchData = async () => {
try {
loading.value = true;
const response = await fetch(url);
data.value = await response.json();
} catch (err: any) {
error.value = err.message;
} finally {
loading.value = false;
}
};
// 在组件挂载时执行请求
onMounted(fetchData);
// 返回响应式数据和方法
return {
data,
loading,
error,
refetch: fetchData, // 提供重新请求的方法
};
}
组件中使用 Hook
<template>
<div>
<div v-if="loading">加载中...</div>
<div v-if="error">错误: {{ error }}</div>
<ul v-if="data">
<li v-for="item in data" :key="item.id">{{ item.name }}</li>
</ul>
<button @click="refetch">重新加载</button>
</div>
</template>
<script setup>
import useFetch from '@/hooks/useFetch'
// // 直接复用请求逻辑
const { data, loading, error, refetch } = useFetch('https://api.example.com/data')
console.log(data.value);
</script>

常用项目解构
srcApp.vue// 项目根组件,一级路由出口api// 接口请求assets// 静态资源icons//svg icon 图标images//image 图标。比如:xxx.pnglogo.png// logo
components// 通用的业务组件。比如:一个组件在多个页面中使用到constants// 常量directives// 自定义指令libs// 通用组件,可用于构建中后台物料库或通用组件库main.js// 入口文件permission.js// 页面权限控制中心router// 路由index.js// 路由处理中心modules// 路由模块mobile-routes.js// 移动端路由pc-routes.js// PC 端路由
store// 全局状态getters.js// 全局状态访问处理index.js// 全局状态中心modules// 状态子模块
styles// 全局样式index.scss// 全局通用的样式处理
utils// 工具模块vendor// 外部供应资源。比如:人类行为认证views// 页面组件。与 components 的区别在于:此处组件对应路由表,以页面的形式展示layout// 用于 PC 端,分割一级路由和二级路由components// 该页面组件下的业务组件index.vue//layout 组件
tailwind.config.js//tailwind css 配置文件,与 src 平级vite.config.js//vite 配置文件,与 src 平级


浙公网安备 33010602011771号