本系列是作者在跟着Vue官网学习时做的笔记,可能并不详尽,读者可以到官网中查看完整内容。本文只讲解带有<script setup>
组合式API。
创建一个 Vue 应用
应用实例(APP)
组件其实就是一个对象(后面会讲),可以从vue文件导出(这样的组件是可复用的),具有一些特定的属性和方法。
通过一个组件来创建app
1
2
3
4
5
|
import { createApp } from 'vue'
const app = createApp({
/* 根组件选项 */
})
|
根组件(vue)
创建app的组件叫根组件
1
2
3
|
import App from './App.vue'
const app = createApp(App)
|
挂载应用
app.mount(“xxx”)将app挂载到index.html来渲染页面
DOM 中的根组件模板
index.html
1
2
3
|
<div id="app">
<button @click="count++">{{ count }}</button>
</div>
|
main.js
1
2
3
4
5
6
7
8
9
10
11
|
import { createApp } from 'vue'
const app = createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
|
当app的根组件没有 template 选项(组合式API也使用术语选项,但是其实表现为属性)时,Vue 将自动使用#app容器的 innerHTML 作为模板
应用配置
app.config对象暴露对应用配置的API,详见官网
app.component(“注册名称”.组件)方法注册应用范围组件:app.component('TodoDeleteButton', TodoDeleteButton)
多个应用实例
一个页面可以绑定多个app
1
2
3
4
5
6
7
8
9
|
const app1 = createApp({
/* ... */
})
app1.mount('#container-1')
const app2 = createApp({
/* ... */
})
app2.mount('#container-2')
|
模板语法
文本插值
template是vue组件的一个选项(属性),和html写法一致
1
|
<span>Message: {{ msg }}</span>
|
{{ msg }}
就是文本插值,会被替换为该组件实例的msg属性值,这种属性一般都是响应式的,js中修改这些属性值会触发页面重新渲染以同步修改
原始 HTML
1
2
|
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
|
指令(其实应该是template元素的属性)由 v-
作为前缀,表明它们是一些由 Vue 提供的特殊 attribute
v-html指令在当前组件实例上,将此元素的 innerHTML 与 rawHtml 属性绑定
不推荐使用v-html,容易造成XSS漏洞
Attribute 绑定
v-bind 指令:template元素属性值和组件属性值单向绑定(改变组件属性值会触发template元素属性值修改)
1
|
<div v-bind:id="dynamicId"></div>
|
简写:
:
1
|
<div :id="dynamicId"></div>
|
布尔型 Attribute:
- 组件属性为真——template元素具有该属性
- 组件属性为假——template元素没有该属性
动态绑定多个值:
即通过"v-bind"(注意不是"v-bind:xxx")属性给template(后面统称页面)元素绑定组件的一个对象属性,对象属性的每一对键值为页面元素的属性名和绑定值
1
|
<div v-bind="objectOfAttrs"></div>
|
使用 JavaScript 表达式
js表达式可以应用于:
- 在文本插值中 (双大括号)
- 在任何 Vue 指令 (以 v- 开头的特殊 attribute) attribute 的值中
仅支持单一表达式
可以在表达式中调用函数
仅能够访问到全局对象列表中的全局对象, app.config.globalProperties 可以添加对象到该列表以供其他组件使用
指令 Directives
v-
开头的特殊template元素属性。
参数 Arguments:指令:
后面的标识符
动态参数:[]
括起来的标识符,应该为组件的一个数据属性
动态参数值应当是一个字符串,或者是 null(显式移除该绑定)
不要使用复杂的动态参数,用计算数学代替
修饰符 Modifiers 以点开头的指令后缀,表示特殊绑定,在执行绑定渲染之前先执行一些其他的任务。
响应式基础
声明响应式状态
reactive() 函数创建一个响应式对象或数组
1
2
3
|
import { reactive } from 'vue'
const state = reactive({ count: 0 })
|
响应式对象其实是 JavaScript Proxy实现的
在 setup() 函数中定义并返回响应式对象或者更新响应式状态的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// 不要忘记同时暴露 increment 函数
return {
state,
increment
}
}
}
|
1
2
3
|
<button @click="increment">
{{ state.count }}
</button>
|
<script setup>
可以省略setup()函数暴露的过程,即js里面定义的响应式对象直接作为组件的属性。
DOM 更新时机:响应式状态改变和DOM(渲染)更新不是同步的,会等到更新周期的“下个时机”更新DOM,在这期间的修改只取最后一次的结果
nextTick()全局API可以让函数在更新完之后执行
1
2
3
4
5
6
7
8
|
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// 访问更新后的 DOM
})
}
|
深层响应性:响应式状态内部的对象或数组改变也会更新。可以定义一个浅层响应式对象
响应式代理 vs. 原始对象:
- reactive() 返回的是一个原始对象的 Proxy,并不相等。
- 修改原始对象不能触发更新,仅使用reactive()的返回值
- 对代理对象调用reactive()会返回它自己
- 响应式对象内的对象仍然是代理
1
2
3
4
5
6
|
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
|
reactive() 的局限性
- 仅对对象类型有效,对 string、number 和 boolean 这样的 原始类型 无效
- 通过属性访问进行追踪,必须始终保持对该响应式对象的相同引用。随意地“替换”一个响应式对象将导致对初始引用的响应性连接丢失
1
2
3
4
|
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })
|
用 ref() 定义响应式变量
ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:
1
2
3
|
import { ref } from 'vue'
const count = ref(0)
|
ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:
1
2
3
4
5
6
7
|
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
|
ref 的 .value 属性也是响应式的,当值为对象类型时,会用 reactive() 自动转换它的 .value
ref 可以响应式地替换整个对象,也可以赋值给本地变量
ref 在模板中的解包:详见官网,我个人认为解包这个语法糖会增加vue的学习难度,对于Ref尽量都不要自动解包
计算属性
computed()方法接收一个箭头函数,返回值为一个计算属性 ref。计算属性可以用.value访问。计算属性和函数体中使用的属性会建立依赖,他们会同步更新
计算属性缓存 vs 方法
相比于在表达式中调用函数,计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。方法调用总是会在重渲染发生时再次执行函数。
可写计算属性
计算属性默认是只读的即只传递一个箭头函数。当传递一个具有getter 和 setter 的对象时为可写计算属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<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>
|
当你再运行 fullName.value = ‘John Doe’ 时,setter 会被调用而 firstName 和 lastName 会随之更新
最佳实践
不要在 getter 中做异步请求或者更改 DOM
避免直接修改计算属性值
尽量不要用可写计算属性
Class 与 Style 绑定
绑定 HTML class
绑定对象:
1
|
<div :class="{ active: isActive }"></div>
|
值为bool的class对象属性和template属性绑定类似,即真为有该属性,假为没有该属性
:class 指令可以和一般的 class attribute 共存
:class 可以绑定一个返回对象的计算属性
绑定数组
可以给 :class 绑定一个数组来渲染多个 CSS class:
1
|
<div :class="[activeClass, errorClass]"></div>
|
可以使用三元表达式:
1
|
<div :class="[isActive ? activeClass : '', errorClass]"></div>
|
可以在数组中嵌套对象
1
|
<div :class="[{ active: isActive }, errorClass]"></div>
|
在组件上使用
只有一个根元素的组件,根元素会继承组件上的class属性,并与该元素上已有的 class 合并
如果组件有多个根元素,需要通过组件的 $attrs 属性指定哪个根元素来接收这个 class。
1
2
3
|
<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
|
绑定内联样式(style)
绑定对象
1
|
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
|
支持 kebab-cased 和camelCase 形式的 CSS 属性 key,推荐使用 camelCase(html不区分大小写,编译器帮忙转换)
可以使用返回样式对象的计算属性
绑定数组
1
|
<div :style="[baseStyles, overridingStyles]"></div>
|
自动前缀
Vue 会为需要浏览器特殊前缀的CSS属性自动加上相应的浏览器前缀
样式多值
1
|
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
|
数组仅会渲染浏览器支持的最后一个值。
条件渲染
v-if
1
|
<h1 v-if="awesome">Vue is awesome!</h1>
|
v-else
1
2
3
4
|
<button @click="awesome = !awesome">Toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
|
必须跟在一个 v-if 或者 v-else-if 元素后面,否则它将不会被识别
v-else-if
和 v-else 类似,必须紧跟在一个 v-if 或一个 v-else-if 元素后面。
可以将多个元素放到<template>
元素中使用该元素上的条件渲染属性统一进行条件渲染,<template>
只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template>
元素。
v-show
1
|
<h1 v-show="ok">Hello!</h1>
|
具有v-show为false的元素会保留在DOM中,v-if为false的元素不会出现在DOM中,v-show仅切换了display style属性
v-show 不支持在 <template>
元素上使用,也不能和 v-else 搭配使用。
v-if vs. v-show
v-if 是“真实的”按条件渲染,确保了在切换时,条件区块内的事件监听器和子组件都会被销毁或重建。
v-if 也是惰性的:初次渲染时条件值为 false,不会做任何事。只有当条件首次变为 true 时才被渲染。
v-show 简单许多,始终会被渲染,只有 CSS display 属性会被切换
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。需要频繁切换,则使用 v-show 较好;条件很少改变,则 v-if 会更合适
v-if 和 v-for
v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行。
列表渲染
v-for
v-for 指令的值需要使用 item in items 形式的特殊语法
1
2
3
|
<li v-for="item in items">
{{ item.message }}
</li>
|
v-for 块中可以完整地访问父作用域内的属性和变量
v-for 也支持使用可选的第二个参数表示当前项的位置索引
1
2
3
|
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
|
可以在定义 v-for 的变量别名时使用解构,和解构函数参数类似:
1
2
3
4
5
6
7
8
|
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
|
多层嵌套的 v-for,每个 v-for 作用域都可以访问到父级作用域:
1
2
3
4
5
|
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
|
可以用of代替in(不推荐,甚至在js里也不推荐,会增加语言学习难度)
v-for 与对象
可以使用 v-for 来遍历一个对象的所有属性(使用Object.keys()实现)
1
2
3
4
5
6
7
8
9
10
11
|
<li v-for="value in myObject">
{{ value }}
</li>
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
|
在 v-for 里使用范围值
类似range(n),n从1开始迭代
1
|
<span v-for="n in 10">{{ n }}</span>
|
上的 v-for
1
2
3
4
5
6
|
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
|
通过 key 管理状态
key 让 Vue 跟踪每个节点,从而重用和重新排序现有的元素
1
2
3
|
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
|
key 绑定基础类型。不要用对象。
组件上使用 v-for
1
|
<MyComponent v-for="item in items" :key="item.id" />
|
数组变化侦测
push(),pop(),shift(),unshift(),splice(),sort(),reverse()
使用这些方法更新响应式数组
展示过滤或排序后的结果
将数组排序后再给v-for,可以使用返回数组的计算属性或者函数
注意,reverse() 和 sort()会修改原数组
1
2
|
- return numbers.reverse()
+ return [...numbers].reverse()
|
事件处理
监听事件
v-on 指令 (简写为 @)
- 内联事件处理器:内联 JavaScript 语句
- 方法事件处理器:组件上定义的方法的属性名或是路径
内联事件处理器
1
2
|
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
|
方法事件处理器
1
2
3
4
5
6
7
|
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
|
1
2
|
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
|
方法事件处理器会自动接收原生 DOM 事件并触发执行
在内联处理器中调用方法
可以在内联事件处理器中调用方法,向方法传入自定义参数以代替原生事件
1
2
3
|
function say(message) {
alert(message)
}
|
1
2
|
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>
|
在内联事件处理器中访问事件参数
使用$event 变量在内联事件处理器中访问原生 DOM 事件, 或者使用箭头函数
1
2
3
4
5
6
7
8
9
|
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
|
1
2
3
4
5
6
7
|
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
|
事件修饰符
- .stop:停止传递
- .prevent:不重新加载页面
- .self:忽略来自子元素的事件
- .capture:使用
capture
捕获模式
- .once:最多被触发一次
- .passive:事件默认行为立即发生,不等待绑定函数执行完成
可以链式调用,先调用的对后面的都有效
按键修饰符
用于键盘事件,KeyboardEvent.key,key使用kebab-case
1
2
|
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
|
按键别名:
.enter
.tab
.delete (捕获“Delete”和“Backspace”两个按键)
.esc
.space
.up
.down
.left
.right
系统按键修饰符:
.ctrl
.alt
.shift
.meta
1
2
3
4
5
|
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
|
.exact 修饰符:仅允许确定组合的事件
1
2
3
4
5
6
7
8
|
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>
|
鼠标按键修饰符
表单输入绑定
基本用法
文本:绑定字符串变量
1
2
|
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />
|
多行文本:绑定字符串变量
1
2
3
|
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
|
<textarea>
中是不支持插值表达式
1
2
3
4
5
|
<!-- 错误 -->
<textarea>{{ text }}</textarea>
<!-- 正确 -->
<textarea v-model="text"></textarea>
|
复选框:绑定bool或者数组
1
2
|
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
|
将多个复选框绑定到同一个数组或集合的值:
1
|
const checkedNames = ref([])
|
1
2
3
4
5
6
7
8
9
10
|
<div>Checked names: {{ checkedNames }}</div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
|
单选按钮:绑定字符串变量
1
2
3
4
5
6
7
|
<div>Picked: {{ 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>
|
选择器:绑定字符串变量或数组
单选
1
2
3
4
5
6
7
8
|
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
|
多选
1
2
3
4
5
6
7
|
<div>Selected: {{ selected }}</div>
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
|
option 可以用 v-for 动态渲染
值绑定
v-model 绑定的值通常是静态的字符串 (或者对复选框是布尔值)
v-bind 可以绑定为非字符串的数据类型,可以将之前绑定静态字符串的value等属性用v-bind绑定
复选框
1
2
3
4
5
|
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no" />
|
true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用
可以通过 v-bind 将其绑定为其他动态值
1
2
3
4
5
|
<input
type="checkbox"
v-model="toggle"
:true-value="dynamicTrueValue"
:false-value="dynamicFalseValue" />
|
单选按钮
1
2
|
<input type="radio" v-model="pick" :value="first" />
<input type="radio" v-model="pick" :value="second" />
|
选择器选项
1
2
3
4
|
<select v-model="selected">
<!-- 内联对象字面量 -->
<option :value="{ number: 123 }">123</option>
</select>
|
修饰符
.lazy:默认是input 事件后更新数据,该修饰符改为在每次 change 事件后更新数据
.number:输入自动转换为数字,无法被 parseFloat() 处理时将返回原始值。number 修饰符会在输入框有 type=“number” 时自动启用
.trim:自动去除用户输入内容中两端的空格
生命周期钩子
直接使用全局API,通常是on+生命周期钩子名称(即图中的红色框),使用camelCase
1
2
3
4
5
6
7
|
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
|
侦听器(watch)
基本示例
侦听数据源类型
全局API watch(数据源,回调函数)
数据源是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:不能直接侦听响应式对象的属性值,可以用一个返回该属性的 getter 函数代替
回调函数可以接收old值和new值。且通常是箭头函数。
1
2
3
4
5
6
7
|
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
|
深层侦听器
watch侦听响应式对象默认是深层侦听的,响应式对象属性发生改变也会触发回调
侦听getter函数时返回不同的对象时才触发回调,可以显式使用deep选项转换为深层侦听
1
2
3
4
5
6
7
8
|
watch(
() => state.someObject,
(newValue, oldValue) => {
// 注意:`newValue` 此处和 `oldValue` 是相等的
// *除非* state.someObject 被整个替换了
},
{ deep: true }
)
|
watchEffect(回调函数)
立即执行回调函数,并追踪函数体中的依赖关系分析出响应源。
watch vs. watchEffect:主要区别是追踪响应式依赖的方式
- watch 只追踪明确侦听的数据源。懒加载,定义时不会执行。
- watchEffect 在副作用发生期间追踪依赖。定义时会执行。
回调的触发时机
Vue 组件更新之前。回调中访问的 DOM 将是被 Vue 更新之前的状态。
flush: ‘post’ 选项指明组件更新后执行
1
2
3
4
5
6
7
|
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
|
watchPostEffect()集成了该选项
1
2
3
4
5
|
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
|
停止侦听器
用同步语句创建的侦听器会在宿主组件卸载时自动停止
异步回调创建的侦听器不会绑定到当前组件上,必须手动停止它,以防内存泄漏
1
2
3
4
5
6
7
8
9
10
11
|
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
|
调用 watch 或 watchEffect 返回的函数停止一个侦听器:
1
2
3
4
|
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
|
模板引用
通过 ref 属性获取底层 DOM 元素
访问模板引用
声明一个同名的 ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
|
在组件挂载后才能访问模板引用,否则为null,编程时需要考虑 null 的情况:
1
2
3
4
5
6
7
|
watchEffect(() => {
if (input.value) {
input.value.focus()
} else {
// 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
}
})
|
v-for 中的模板引用
v-for元素ref attribute 使用数组ref绑定,ref数组不保证和html相同
函数模板引用
ref attribute 绑定(使用v-bind或者:
)为一个函数,组件更新时被调用,第一个参数为DOM元素引用
组件上的 ref
引用中获得的值是组件实例
<script setup>
的组件是默认私有的。一个父组件无法访问到一个使用了子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:
1
2
3
4
5
6
7
8
9
10
11
|
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
|
尽量避免使用组件ref,减少组件间的耦合
组件基础
定义一个组件
.vue 文件:单文件组件 (简称 SFC)
1
2
3
4
5
6
7
8
9
|
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
|
使用组件
<script setup>
组件将会以默认导出的形式被暴露给外部,导入的组件都在模板中直接可用。
全局地注册一个组件在当前应用中的任何组件上都可以使用
组件可以被重用任意多次,每一个组件都维护着自己的状态
传递 props
defineProps 宏:声明的 props 会自动暴露给模板
1
2
3
4
5
6
7
8
|
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
|
返回一个对象,其中包含了可以传递给组件的所有 props
1
2
|
const props = defineProps(['title'])
console.log(props.title)
|
一个组件可以有任意多的 props,默认情况下,所有 prop 都接受任意类型的值
父组件传递数据给子组件:
1
2
3
|
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
|
v-bind 可以传递动态 prop 值
监听事件
父组件可以通过 v-on 或 @ 来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样。
1
2
3
4
|
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
|
子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件
1
2
3
4
5
6
7
|
<!-- BlogPost.vue, 省略了 <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
|
通过 defineEmits 宏来声明需要抛出的事件
1
2
3
4
5
|
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>
|
通过插槽来分配内容
子组件中传递HTML内容
父组件:
1
2
3
|
<AlertBox>
Something bad happened.
</AlertBox>
|
子组件:
1
2
3
4
5
6
7
8
9
10
11
12
|
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
|
<slot>
元素表示父组件html渲染位置
动态组件
通过组件的is属性实现
1
2
|
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>
|
值可以是:
<KeepAlive>
组件强制被切换掉的组件仍然保持“存活”的状态
1
2
3
4
|
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
|
DOM 模板解析注意事项
见官网,没啥必要学,反正都是直接用单文件组件开发