Vue入门
一、简介
Vue是一款用于构建用户界面的JavaScript框架,它基于标准HTML、CSS和JavaScript构建,并提供了一套声明式的、组件化的编程模型,可以高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
二、语法
1、创建应用
可以通过createApp函数创建一个应用实例,并传入一个“根组件”来创建应用。
- 创建应用
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'
const app = createApp(App)
// 挂载到DOM元素上
app.mount('#app')
- 应用配置
可以通过应用实例的.config对象进行应用级配置,例如配置错误处理器、注册全局组件或指令等。
app.config.errorHandler = (err) => {
/* 处理错误 */
}
// 全局注册
app.component('MyComponent', MyComponent)
-
説明
-
挂载
.mount()方法必须在应用配置之后调用,它返回的是根组件实例。 -
多应用实例
可以在同一个页面上创建多个独立的Vue应用,每个应用都有自己的作用域。
-
2、模板语法
Vue的模板语法基于HTML,能够声明式地将组件实例的数据绑定到DOM上。
- 文本插值
双大括号{{ }}用于文本插值,数据会被解释为纯文本。
<span>Message: </span>
- 原始HTML
可以使用v-html指令输出真正的HTML。但需注意XSS风险,只对可信内容使用。
<span v-html="rawHtml"></span>
-
指令
指令是带有
v-前缀的特殊attribute,它的期望值是一个JavaScript表达式。一个指令的任务是在其表达式的值变化时响应式地更新DOM。例如:
<p v-if="seen">Now you see me</p>- 参数
某些指令会需要一个“参数”:在指令名后通过一个冒号隔开做标识。例如用
v-bind指令来响应式地更新一个HTML attribute:<a v-bind:href="url"> ... </a> <!-- 简写 --> <a :href="url"> ... </a> <a v-on:click="doSomething"> ... </a> <!-- 简写 --> <a @click="doSomething"> ... </a>- 动态参数
在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:
<a v-bind:[attributeName]="url"> ... </a> <!-- 简写 --> <a :[attributeName]="url"> ... </a>上面的
attributeName会作为一个JavaScript表达式被动态执行,计算得到的值会被用作最终的参数。 -
属性绑定
可以使用
v-bind指令响应式地绑定HTML属性,简写为::<div :id="dynamicId"></div>- 同名简写
如果属性名与绑定的JavaScript变量名相同,可直接写,例如:
:id:<div :id></div>- 布尔型属性
当值为真值或空字符串时,此属性(attribute)存在;如果为假值(例如null、 undefined、 false)时,属性会被移除。
<button :disabled="isButtonDisabled">Button</button> -
使用JavaScript表达式
Vue支持在模板中绑定完整的JavaScript表达式,但每个绑定只能包含单一表达式(一段能够被求值的 JavaScript 代码)。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
<div :id="`list-${id}`"></div>
下面的例子都是无效的:
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}
<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}
- 修饰符
以点.开头的特殊后缀,表示指令以特殊方式绑定。例如.prevent告诉v-on指令调用 event.preventDefault()。
<form @submit.prevent="onSubmit">...</form>
3、响应式基础
-
声明响应式状态
- 选项式API
使用data选项,它返回一个对象,Vue会将其属性变为响应式。
export default { data() { return { count: 0 } } }- 组合式API
在组合式API中,推荐使用
ref()函数来声明响应式状态:import { ref } from 'vue' // 适用于基本类型,通过 .value 访问 const count = ref(0) console.log(count) // { value: 0 } console.log(count.value) // 0 count.value++ console.log(count.value) // 1如果要在组件模板中访问ref,需要在组件的
setup()函数中声明并返回它们:import { ref } from 'vue' export default { // `setup` 是一个特殊的钩子,专门用于组合式 API。 setup() { const count = ref(0) // 将 ref 暴露给模板 return { count } } }<div>8</div>ref创建了一个包含value属性的对象。通过.value属性,Vue可以追踪其访问和修改,从而实现响应性。在模板中使用时,ref会自动“解包”,无需写.value。另一种声明响应式状态的方式是使用
reactive()API,与将内部值包装在特殊对象中的ref不同,reactive()将使对象本身具有响应性:// 适用于对象/数组,可直接访问属性 import { reactive } from 'vue' const state = reactive({ count: 0 })在模板中使用:
<button @click="state.count++"> </button>reactive()返回的是一个原始对象的代理Proxy,它和原始对象是不相等的;只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用Vue的响应式系统的最佳实践是仅使用你声明对象的代理版本。const raw = {} const proxy = reactive(raw) // 代理对象和原始对象不是全等的 console.log(proxy === raw) // falsereactive的局限是只能用于对象类型,不能替换整个对象,而且解构时会丢失响应性。因此,官方推荐使用ref()作为主要API。let state = reactive({ count: 0 }) // 上面的 ({ count: 0 }) 引用将不再被追踪,响应性连接已丢失! state = reactive({ count: 1 }) // 当解构时,count 已经与 state.count 断开连接 let { count } = state // 不会影响原始的 state count++ // 该函数接收到的是一个普通的数字,并且无法追踪 state.count 的变化,必须传入整个对象以保持响应性 callSomeFunction(state.count) -
<script setup>
在setup()函数中手动暴露大量的状态和方法非常繁琐,因此可以使用<script setup>来大幅度地简化代码:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
8
</button>
</template>
<script setup>中的顶层的导入、声明的变量和函数可以在同一组件的模板中直接使用。可以理解为模板是在同一作用域内声明的一个JavaScript函数。
- DOM更新时机
Vue的DOM更新是异步的,修改响应式状态后,DOM不会立即更新;如果要等待更新完成,可以使用 nextTick()。
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// 现在DOM已更新
}
4、方法与计算属性
- 方法
在选项式API中,使用methods选项为组件添加方法,方法中的this会自动绑定到当前组件实例。
export default {
methods: {
increment() {
this.count++
}
}
}
- 计算属性
对于包含复杂逻辑的响应式数据,应使用计算属性。它基于其响应式依赖进行缓存,只有依赖变化时才会重新计算。
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span></span>
</template>
上面的computed()方法接收一个getter函数,返回值为一个计算属性ref,赋值给publishedBooksMessage(计算属性);和其他一般的ref类似,可以通过publishedBooksMessage.value访问计算结果。计算属性ref也会在模板中自动解包,因此在模板表达式中引用时无需添加.value。
- 可写计算属性
计算属性默认是只读的,如果需要用到“可写”的属性,则可以通过同时提供getter和setter方法来创建:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
- 方法与计算属性的区别
计算属性有缓存,方法则没有。若依赖不改变,多次访问计算属性会直接返回缓存结果,性能更优。
5、类与样式绑定
数据绑定的一个常见需求场景是操纵元素的CSS class列表和内联样式,可以和其他attribute一样使用 v-bind将它们和动态的字符串绑定。但在处理比较复杂的绑定时,通过拼接生成字符串比较麻烦且容易出错。因此,Vue专门为class和style的v-bind用法提供了特殊的功能增强:除了字符串外,表达式的值也可以是对象或数组。
-
绑定HTML Class
- 对象
根据数据动态切换class。
<div :class="{ active: isActive, 'text-danger': hasError }"></div>- 数组
绑定多个class。
<div :class="[activeClass, errorClass]"></div>- 在组件上使用
class会被自动添加到组件的根元素上,并与其已有class合并。例如:
组件MyComponent:
<p class="foo bar">Hi!</p>在使用时添加一些 class:
<!-- 在使用组件时 --> <MyComponent class="baz boo" />渲染出的HTML为:
<p class="foo bar baz boo">Hi!</p> -
绑定内联样式
- 对象
推荐使用
camelCase:const activeColor = ref('red') const fontSize = ref(30)<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>也支持
kebab-cased形式的CSS属性key (对应其 CSS 中的实际名称),例如:const styleObject = reactive({ color: 'red', fontSize: '30px' })<div :style="{ 'font-size': fontSize + 'px' }"></div> <!--直接绑定一个样式对象可以使模板更加简洁--> <div :style="styleObject"></div>- 数组
绑定一个包含多个样式对象的数组,这些对象会被合并后渲染到同一元素上:
<div :style="[baseStyles, overridingStyles]"></div>- 自动前缀
Vue会为需要浏览器特殊前缀的CSS属性自动添加前缀。Vue是在运行时检查该属性是否支持在当前浏览器中使用。如果浏览器不支持某个属性,那么将尝试加上各个浏览器特殊前缀,以找到哪一个是被支持的。
6、条件渲染
v-if/v-else-if/v-else
“真实的”按条件渲染,确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。同时也是惰性的:如果在初次渲染时条件值为false,则不会做任何事;只有当条件首次变为true时才被渲染。适合切换频率低的场景。
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
<template>上的v-if:可用于包裹多个元素,但最终渲染结果不会包含<template>本身。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
v-else和v-else-if也可以在 上使用。
v-show
元素始终被渲染,仅切换其display CSS属性,适合频繁切换的场景。
<h1 v-show="ok">Hello!</h1>
v-show不支持在<template>元素上使用。
7、列表渲染
-
v-for基于数组或对象渲染列表,语法为
item in items,第二个参数为索引或键名(可选)。- 数组
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])<li v-for="item in items"> </li> <li v-for="(item, index) in items"> - </li>- 对象
const myObject = reactive({ title: 'How to do lists in Vue', author: 'Jane Doe', publishedAt: '2016-04-10' })<ul> <li v-for="value in myObject"> </li> </ul> <!--第二个参数表示属性名--> <li v-for="(value, key) in myObject"> : </li> <!--第三个参数表示位置索引--> <li v-for="(value, key, index) in myObject"> . : </li> -
key
为每个列表项提供一个唯一的key,可以帮助Vue高效地跟踪节点状态,实现重用和重新排序。推荐在任何可能的时候都使用key。
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
使用<template v-for>时,key应该被放置在<template>这个容器上:
<template v-for="todo in todos" :key="todo.name">
<li></li>
</template>
-
数组变化侦测
- 变更方法(改变原数组)
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()。- 替换方法(返回新数组)
filter()、concat()、slice()不会更改原数组,而总是返回一个新数组。// `items` 是一个数组的 ref items.value = items.value.filter((item) => item.message.match(/Foo/)) -
展示过滤或排序后的结果
建议使用计算属性来处理显示过滤或排序后的列表,以避免直接修改原始数据。
const numbers = ref([1, 2, 3, 4, 5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
<li v-for="n in evenNumbers"></li>
8、事件处理
可以使用v-on指令(简写为@)来监听DOM事件,并在事件触发时执行对应的JavaScript语句。用法:v-on:click="handler"或@click="handler"。
-
事件处理器
事件处理器(handler)的值可以是:内联事件处理器(直接执行JavaScript语句)或方法事件处理器(指向组件中定义的方法名)。
- 内联事件处理器
事件被触发时执行的内联JavaScript语句(与onclick类似),内联事件处理器通常用于简单场景,例如:
const count = ref(0)<button @click="count++">Add 1</button> <p>Count is: 8</p>- 方法事件处理器
当事件处理器的逻辑比较复杂时,内联代码方式变得不够灵活,此时可以使用
v-on指定一个方法名或对某个方法的调用:const name = ref('Vue.js') function greet(event) { alert(`Hello ${name.value}!`) // `event` 是 DOM 原生事件 if (event) { alert(event.target.tagName) } }<!-- `greet` 是上面定义过的方法名 --> <button @click="greet">Greet</button> -
事件修饰符
事件修饰符简化了DOM事件细节的处理,例如:
stop(停止冒泡)、.prevent(阻止默认行为)、.once(只触发一次)等,可以链式调用。<!-- 单击事件将停止传递 --> <a @click.stop="doThis"></a> <!-- 提交事件将不再重新加载页面 --> <form @submit.prevent="onSubmit"></form> <!-- 修饰语可以使用链式书写 --> <a @click.stop.prevent="doThat"></a> <!-- 也可以只有修饰符 --> <form @submit.prevent></form> <!-- 仅当 event.target 是元素本身时才会触发事件处理器 --> <!-- 例如:事件处理器不来自子元素 --> <div @click.self="doThat">...</div> -
按键修饰符
在监听键盘事件时,经常需要检查特定的按键。Vue允许在v-on或@监听按键事件时添加按键修饰符。例如:
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
可以直接使用KeyboardEvent.key暴露的按键名称作为修饰符,但需要转为kebab-case形式。例如:
<input @keyup.page-down="onPageDown" />
- 按键别名
Vue为一些常用的按键提供了别名:.enter、.tab、.delete、.esc、.space、.up、.down、.left、.right。
- 系统按键修饰符
也可以使用.ctrl、.alt、.shift、.meta等系统按键修饰符来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。
.exact
使用.exact修饰符可台精确控制触发事件所需的系统修饰符的组合。
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>
- 鼠标按键修饰符
.left、.right、.middle这些修饰符将处理程序限定为由特定鼠标按键触发的事件。
9、表单输入绑定
-
v-model使用
v-model指令可以简化将表单输入框的内容同步到JavaScript中相应的变量过程,即在表单<input>、<textarea>、<select>元素上创建双向数据绑定,它会根据控件类型自动使用正确的属性和事件。例如:<input :value="text" @input="event => text = event.target.value">使用
v-model可以简化为:<input v-model="text">- 文本/多行文本
绑定value并监听input事件。
<p>Message is: </p> <input v-model="message" placeholder="edit me" />在
<textarea>中不支持插值表达式,需要使用v-model来替代:<!-- 错误 --> <textarea></textarea> <!-- 正确 --> <textarea v-model="text"></textarea>- 复选框/单选按钮
绑定checked并监听change事件。
<input type="checkbox" id="checkbox" v-model="checked" /> <label for="checkbox"></label> <div>Picked: </div> <input type="radio" id="one" value="One" v-model="picked" /> <label for="one">One</label> <input type="radio" id="two" value="Two" v-model="picked" /> <label for="two">Two</label>- 选择器
绑定value并监听change事件。
<div>Selected: </div> <select v-model="selected"> <option disabled value="">Please select one</option> <option>A</option> <option>B</option> <option>C</option> </select> -
值绑定
对于单选按钮、复选框和选择器选项,
v-model绑定的值通常是静态的字符串或布尔值,可以通过v-bind将复选框、单选按钮等的值绑定到动态数据,支持非字符串值。- 复选框
true-value和false-value是Vue特有的属性,仅支持和v-model配套使用。<!--这里 toggle 属性的值会在选中时被设为 'yes',取消选择时设为 'no'。--> <input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />可以通过
v-bind将其绑定为其他动态值:<input type="checkbox" v-model="toggle" :true-value="dynamicTrueValue" :false-value="dynamicFalseValue" />- 单选按钮
<!--pick 会在第一个按钮选中时被设为 first,在第二个按钮选中时被设为 second。--> <input type="radio" v-model="pick" :value="first" /> <input type="radio" v-model="pick" :value="second" />- 选择器选项
<select v-model="selected"> <!-- 内联对象字面量,当选项被选中,selected 会被设为该对象字面量值 { number: 123 } --> <option :value="{ number: 123 }">123</option> </select> -
修饰符
.lazy
默认情况下,
v-model会在每次input事件后更新数据,使用.lazy将其改为在每次change事件后更新数据:<!-- 在 "change" 事件后同步更新而不是 "input" --> <input v-model.lazy="msg" />.number
自动将用户输入转为数字:
<input v-model.number="age" />如果该值无法被
parseFloat()处理,那么将返回原始值。特别是当输入为空时 (例如用户清空输入字段之后),会返回一个空字符串。.trim
自动去除输入内容两端的空格:
<input v-model.trim="msg" />
10、侦听器
在Vue中,侦听器用于在响应式数据变化时执行特定逻辑,例如:异步请求、操作DOM、或根据一个值的变化去修改另一个值等。
- watch
在组合式API中,可以使用watch函数在每次响应式状态发生变化时触发回调函数,watch的第一个参数为数据源,可以是:ref、reactive对象、getter函数或由以上类型组成的数组。
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('')
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.includes('?')) {
// 执行异步操作
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
}
})
</script>
在选项式API中可以通过watch选项实现:
export default {
data() {
return {
question: '',
answer: ''
}
},
watch: {
// 监听 question 的变化,参数为新值和旧值
question(newQuestion, oldQuestion) {
if (newQuestion.includes('?')) {
this.getAnswer()
}
}
},
methods: {
async getAnswer() { /* ... */ }
}
}
-
深层侦听器
默认情况下,watch是浅层的:即只有数据源本身的引用发生变化才会触发,嵌套属性的变化不会触发。
可以使用
reactive()或deep选项实现:- reactive
// 组合式:直接传入 reactive 对象 const state = reactive({ user: { name: '张三' } }) watch(state, () => { console.log('state 中任何属性变化都会触发') }) state.user.name = '李四' // 触发侦听器 // 如果需要侦听某个嵌套属性,必须使用 getter watch(() => state.user.name, (newName) => { console.log('name 变化了', newName) })- deep
组合式:
watch(() => state.someObject, (newVal, oldVal) => { // 仅当 someObject 被替换时触发 }, { deep: true }) // 加上 deep 后,内部属性变化也会触发选项式:
export default { watch: { someObject: { handler(newVal, oldVal) { /* ... */ }, deep: true } } }深层侦听会递归遍历对象的所有属性,当数据庞大时开销较大,请谨慎使用。
-
即时回调
如果希望侦听器在创建时立即执行一次,而不必等待数据第一次变化时,可以使用immediate。例如:进入页面就拉取数据,之后每次参数变化再重新拉取。
组合式:
watch(source, (newVal) => {
// 立即执行一次,之后每次 source 变化再执行
}, { immediate: true })
选项式:
export default {
watch: {
question: {
handler(newQuestion) { /* ... */ },
immediate: true
}
}
}
- 一次性侦听器
如果只希望侦听器在数据变化时仅触发一次(不包括立即执行的那次),可以使用once。
watch(source, () => {
// 仅第一次变化时执行
}, { once: true })
- watchEffect
watchEffect会自动追踪其回调函数中使用到的所有响应式数据,当其中任何一个变化时,回调会重新执行。它相当于一个更简洁的watch + immediate: true,但无法获取旧值。
import { watchEffect } from 'vue'
const count = ref(0)
const name = ref('张三')
watchEffect(() => {
// 会自动追踪 count.value 和 name.value
console.log(`count: ${count.value}, name: ${name.value}`)
})
// 立即输出:count: 0, name: 张三
count.value++ // 输出:count: 1, name: 张三
name.value = '李四' // 输出:count: 1, name: 李四
-
副作用清理
Vue提供了
onWatcherCleanup或回调参数onCleanup来实现副作用的清理。例如:当侦听器中执行异步操作(如fetch),在上一次请求完成前,数据又发生了变化,此时应当取消过时的请求,避免状态冲突。- onWatcherCleanup
<script setup> import { watch, onWatcherCleanup } from 'vue' const id = ref(1) watch(id, (newId) => { const controller = new AbortController() fetch(`/api/${newId}`, { signal: controller.signal }) .then(res => res.json()) .then(data => { /* 处理数据 */ }) // 注册清理函数,当 id 再次变化时,会先调用此函数 onWatcherCleanup(() => { controller.abort() // 取消过时的请求 }) }) </script>- onCleanup
watch(id, (newId, oldId, onCleanup) => { const controller = new AbortController() fetch(`/api/${newId}`, { signal: controller.signal }) onCleanup(() => controller.abort()) }) -
回调触发时机
Vue默认会在父组件更新之后、当前组件的DOM更新之前调用侦听器回调。如果你需要访问更新后的DOM,可以指定flush: 'post'。
// 组合式
watch(source, callback, { flush: 'post' })
watchEffect(callback, { flush: 'post' })
// 快捷写法
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
// 在 DOM 更新后执行
})
如果需要在Vue进行任何更新之前触发,可以使用flush: 'sync':
watch(source, callback, {
flush: 'sync'
})
watchEffect(callback, {
flush: 'sync'
})
// 快捷写法
import { watchSyncEffect } from 'vue'
watchSyncEffect(() => {
/* 在响应式数据变化时同步执行 */
})
- 停止侦听器
在setup()或<script setup>中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,必须手动停止以防内存泄漏。
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
要手动停止一个侦听器,需要调用watch或watchEffect返回的函数:
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
一般情况很少需要异步创建侦听器,尽可能选择同步创建。如果需要等待一些异步数据,可以使用以下方式:
// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 数据加载后执行某些操作...
}
})
11、模板引用
Vue的声明性渲染模型抽象了大部分对DOM的直接操作,但在某些情况下,如果仍然需要直接访问底层DOM元素,可以使用ref属性。
<input ref="input">
ref允许在一个特定的DOM元素或子组件实例被挂载后,获得对它的直接引用。使用场景:例如在组件挂载时将焦点设置到一个input元素上,或者在一个元素上初始化一个第三方库。
<script setup>
// 在组合式 API 中获取引用,可以使用辅助函数 useTemplateRef()
import { useTemplateRef, onMounted } from 'vue'
// 第一个参数必须与模板中的 ref 值匹配
const input = useTemplateRef('my-input')
onMounted(() => {
// 只可在组件挂载后才能访问模板引用
input.value.focus()
})
</script>
<template>
<input ref="my-input" />
</template>
模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例:
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = useTemplateRef('child')
onMounted(() => {
// childRef.value 将持有 <Child /> 的实例
})
</script>
<template>
<Child ref="child" />
</template>
12、组件
- 定义组件
通常使用单文件组件(.vue文件),它在一个文件中封装了模板、脚本和样式。
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me 8 times.</button>
</template>
如果不使用构建步骤时,一个Vue组件以一个包含Vue特定选项的JavaScript对象来定义:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me 8 times.
</button>`
}
-
组件注册
一个Vue组件在使用前需要先被“注册”,有两种方式:全局注册和局部注册。
- 全局注册
通过使用Vue应用实例的
.component()方法,可以让组件在当前Vue应用中全局可用。import { createApp } from 'vue' const app = createApp({}) app.component( // 注册的名字 'MyComponent', // 组件的实现 { /* ... */ } )如果使用单文件组件,可以注册被导入的
.vue文件:import MyComponent from './App.vue' app.component('MyComponent', MyComponent).component()方法支持链式调用。app .component('ComponentA', ComponentA) .component('ComponentB', ComponentB) .component('ComponentC', ComponentC)- 局部注册
局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对tree-shaking更加友好。
在使用
<script setup>的单文件组件中,导入的组件可以直接在模板中使用,无需注册:<script setup> import ComponentA from './ComponentA.vue' </script> <template> <ComponentA /> </template>如果没有使用
<script setup>,则需要使用components选项来显式注册:import ComponentA from './ComponentA.js' export default { components: { ComponentA }, setup() { // ... } } -
使用组件
在父组件中导入并注册(全局或局部)后,即可作为自定义标签使用。例如:把计数器组件放在了一个叫做ButtonCounter.vue的文件中,这个组件将会以默认导出的形式被暴露给外部。
<!--通过 <script setup>,导入的组件都在模板中直接可用。-->
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
- 传递props
父组件通过props属性向子组件传递数据,子组件通过defineProps(组合式)或props选项(选项式)声明接收。props是一种特别的属性,可以在组件上声明注册:
<script setup>
defineProps(['title'])
</script>
<template>
<h4></h4>
</template>
如果没有使用<script setup>,props必须以props选项的方式声明,props对象会作为setup()函数的第一个参数被传入:
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}
defineProps是一个仅<script setup>中可用的编译宏命令,并不需要显式地导入,声明的props会自动暴露给模板。defineProps会返回一个对象,其中包含了可以传递给组件的所有props:
const props = defineProps(['title'])
console.log(props.title)
例如,在父组件中会有如下的一个博客文章数组:
const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])
之后可以使用v-for来渲染:
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
- 监听事件
在Vue中,通过自定义事件实现子组件向父组件传递消息或数据,父组件通过v-on或@监听子组件触发的事件,子组件则通过$emit(选项式 API)或defineEmits(组合式 API)来触发事件。
$emit
$emit('事件名', 参数1, 参数2, ...)
子组件Child.vue:
<template>
<button @click="sendMessage">点击向父组件发送消息</button>
</template>
<script>
export default {
methods: {
sendMessage() {
// 触发名为 'greet' 的自定义事件,并携带一个字符串参数
this.$emit('greet', 'Hello from child component!')
}
}
}
</script>
父组件:
使用@greet="handleGreet"监听子组件触发的greet事件。
<template>
<div>
<Child @greet="handleGreet" />
<p>收到子组件的消息:</p>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: { Child },
data() {
return {
messageFromChild: ''
}
},
methods: {
handleGreet(msg) {
this.messageFromChild = msg
}
}
}
</script>
defineEmits
defineEmits()方法声明可触发的事件,它返回的emit函数用于触发事件。
子组件Child.vue:
<template>
<button @click="sendMessage">点击向父组件发送消息</button>
</template>
<script setup>
// 声明该组件可以触发的自定义事件
const emit = defineEmits(['greet'])
const sendMessage = () => {
// 触发 'greet' 事件,携带参数
emit('greet', 'Hello from child component!')
}
</script>
父组件:
<template>
<div>
<Child @greet="handleGreet" />
<p>收到子组件的消息:</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const messageFromChild = ref('')
const handleGreet = (msg) => {
messageFromChild.value = msg
}
</script>
使用defineEmits不仅能让代码更清晰,还能在TypeScript中提供类型检查。
- 插槽
如果想实现和HTML元素一样向组件中传递内容,可以通过<slot>元素实现。
AlertBox.vue:
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
父组件:
<AlertBox>
Something bad happened.
</AlertBox>
最终渲染出来的内容如下:
This is an Error for Demo Purposes
Something bad happened.
- 动态组件
如果需要在两个组件间来回切换,例如Tab界面,可以通过<component>元素和特殊的is属性来实现:
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>
被传给:is的值可以是被注册的组件名或导入的组件对象。当使用<component :is="...">来在多个组件间作切换时,被切换掉的组件会被卸载。
13、生命周期
每个Vue组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听、编译模板、挂载实例到DOM,以及在数据改变时更新DOM等。在此过程中,可以通过生命周期钩子函数在特定阶段运行指定的代码。
-
注册生命周期钩子函数
例如,使用
onMounted可以在组件完成初始渲染并创建DOM节点后运行代码:<script setup> import { onMounted } from 'vue' onMounted(() => { console.log(`the component is now mounted.`) }) </script>还有其他一些钩子,会在实例生命周期的不同阶段被调用,常用的如下:
- beforeCreate
实例初始化之后被调用。
- created
实例创建完成后被立即调用。此时,组件实例已处理完响应式状态,但尚未挂载。可以在此进行数据请求、设置事件监听等。
- beforeMount
在挂载开始之前被调用。
- mounted
实例被挂载后调用,此时DOM已生成。常用于操作DOM、初始化第三方库。
- beforeUpdate
响应式数据变化后,DOM重新渲染前调用。可用于在更新前访问现有DOM。
- updated
由于数据变化导致的DOM重新渲染完成后调用。执行时请避免修改状态,以免触发无限更新循环。
- beforeUnmount
实例卸载之前调用。此阶段实例仍然完全可用。
- unmounted
实例卸载后调用。可以在此实现清理逻辑、移除事件监听器等。