参考资料
基础
简介
- 单文件组件 SFC
API分隔
传入createApp的是根组件
-
挂载应用
应用实例必须在调用了 .mount() 方法后才会渲染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串:
应用配置
```javascript app.config.errorHandler = (err) => { / 处理错误 / }
app.component(‘TodoDeleteButton’, TodoDeleteButton) // 注册组件
<a name="Jt45x"></a>
### 多个应用实例
<a name="sq01m"></a>
## 模板语法
<a name="pq8hH"></a>
### 文本插值
- “Mustache”语法 (即双大括号)
<a name="CdQC9"></a>
### 原始HTML
```javascript
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
Atrribute绑定
如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>
布尔型 Attribute
当 isButtonDisabled 为真值或一个空字符串 (即
复制代码 <a name="LM59u"></a>
### 使用 JavaScript 表达式
- 在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
- 在文本插值中 (双大括号)
- 在任何 Vue 指令 (以 v- 开头的特殊 attribute) attribute 的值中
- 仅支持表达式
- 调用函数
```html
<span :title="toTitleDate(date)">
{{ formatDate(date) }}
</span>
受限的全局访问
- 模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。
- 可以自行在 app.config.globalProperties 上显式地添加
指令
带v-的特殊atrribute
指令 attribute 的期望值为一个 JavaScript 表达式 (之后要讨论到的 v-for 和 v-on 将会是例外)。
参数
动态参数
动态参数值的限制
- 动态参数期望结果为一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。任何其他非字符串的值都将触发一个警告。
- 动态参数语法的限制
- 如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,
- 避免在名称中使用大写字母,因为浏览器会强制将其转换为小写: ```html
复制代码 <a name="cm4iD"></a>
### 修饰符
- .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault():

<a name="zOTnC"></a>
## 响应式基础
<a name="t7QHk"></a>
### 声明响应式状态
```javascript
export default {
data() {
return {
someObject: {}
}
},
mounted() {
const newObject = {}
this.someObject = newObject
console.log(newObject === this.someObject) // false 和 Vue 2 不同,
//原始的 newObject 不会变为响应式:确保始终通过 this. 来访问响应式状态。
}
}
声明方法
Vue 自动为 methods 中的方法绑定了永远指向组件实例的 this。这确保了方法在作为事件监听器或回调函数时始终保持正确的 this。你不应该在定义 methods 时使用箭头函数,因为这会阻止 Vue 的自动绑定。
复制代码 export default {
methods: {
increment: () => {
// BAD: no `this` access here!
}
}
}
DOM 更新时机
注意 DOM 的更新并不是同步的。相反,Vue 会将它们推入更新循环的 “下个 tick” 执行以确保无论改变了多少个状态,每个需要更新的组件都只更新一次。
- 若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API: ```javascript import { nextTick } from ‘vue’
export default { methods: { increment() { this.count++ nextTick(() => { // access updated DOM }) } } }
复制代码 <a name="qdAbY"></a>
#### 深层响应性
- 在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。
- 也可以创建一个 [浅层 ref](https://staging-cn.vuejs.org/api/reactivity-advanced.html#shallowref) 和 [浅层响应式对象](https://staging-cn.vuejs.org/api/reactivity-advanced.html#shallowreactive)。它们仅在顶层具有响应性,一般仅在某些特殊场景中需要。
```javascript
export default {
data() {
return {
obj: {
nested: { count: 0 },
arr: ['foo', 'bar']
}
}
},
methods: {
mutateDeeply() {
// 以下都会按照期望工作
this.obj.nested.count++
this.obj.arr.push('baz')
}
}
}
有状态方法
- 防止不同实例的方法彼此影响
复制代码 export default {
created() {
// 每个实例都有了自己的预置防抖的处理函数
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 最好是在组件卸载时
// 清除掉防抖计时器
this.debouncedClick.cancel()
},
methods: {
click() {
// ... 对点击的响应 ...
}
}
}
计算属性
```javascript
export default {
data() {
return {
author: {
name: ‘John Doe’,
books: [
‘Vue 2 - Advanced Guide’,
‘Vue 3 - Basic Guide’,
‘Vue 4 - The Mystery’
]
}
}
},
computed: {
// 一个计算属性的 getter
publishedBooksMessage() {
// this
指向当前组件实例
return this.author.books.length > 0 ? ‘Yes’ : ‘No’
}
}
}
……
Has published books:
{{ publishedBooksMessage }} 复制代码 <a name="euig2"></a>
### 计算属性缓存 vs 方法
- **计算属性值会基于其响应式依赖被缓存,**一个计算属性仅会在其响应式依赖更新时才重新计算
- 相比之下,方法调用**总是**会在重渲染发生时再次执行函数。
<a name="uvNcH"></a>
### 可写计算属性
```javascript
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
}
计算函数不应有副作用
计算属性的计算函数应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。举个例子,不要在计算函数中做异步请求或者更改 DOM!一个计算属性的声明中描述的是如何根据其他值派生一个值。因此计算函数的职责应该仅为计算和返回该值。在之后的指引中我们会讨论如何使用监听器根据其他响应式状态的变更来创建副作用。
避免直接修改计算属性值
从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。因此更改快照是没有意义的,因此,计算属性的返回值应该被视为只读的,并且永远不会发生突变。应该更新它所依赖的源状态,以触发新一次计算。
类与样式绑定
绑定HTML对象
绑定对象
复制代码 #######
<div :class="{ active: isActive }"></div>
data() {
return {
isActive: true,
hasError: false
}
}
######
<div :class="classObject"></div>
data() {
return {
classObject: {
active: true,
'text-danger': false
}
}
}
####
<div :class="classObject"></div>
data() {
return {
isActive: true,
error: null
}
},
computed: {
classObject() {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
绑定数组
复制代码 data() {
return {
activeClass: 'active',
errorClass: 'text-danger'
}
}
<div :class="[activeClass, errorClass]"></div>
<div :class="[isActive ? activeClass : '', errorClass]"></div>
<div :class="[{ active: isActive }, errorClass]"></div>
和组件配合
使用组件时渲染后会自动叠加属性
camelCase 与 kebab-cased 形式 ```javascript data() { return { activeColor: ‘red’, fontSize: 30 } }
///////////// data() { return { styleObject: { color: ‘red’, fontSize: ‘13px’ } } }
复制代码 <a name="iXdQq"></a>
#### 绑定数组
<a name="JY6rL"></a>
#### 自动前缀
- 当你在 :style 中使用了需要[浏览器特殊前缀](https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix)的 CSS 属性时,Vue 会自动为他们加上相应的前缀。
<a name="qQ3DX"></a>
#### 样式多值
<a name="MWNEa"></a>
## 条件渲染
<a name="ugV0c"></a>
### v-if
<a name="UI5RX"></a>
### v-else
<a name="cO7r8"></a>
### v-else-if
```javascript
<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>
上的 v-if因为 v-if 是一个指令,他必须依附于某个元素。但如果我们想要切换不只一个元素呢?在这种情况下我们可以在一个 元素上使用 v-if,这只是一个不可见的包裹层,最后渲染的结果不会包含这个 元素。
v-show
- 不同之处在于 v-show 会在 DOM 渲染中保留该节点;v-show 仅切换了该元素的 display CSS 属性。
v-show 不支持在 元素上使用,也没有 v-else 来配合。
v-if vs v-show
v-if 是“真实的”按条件渲染,因为它确保了条件区块内的事件监听器和子组件都会在切换时被销毁与重建。
v-if 是懒加载的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块会直到条件首次变为 true 时才渲染。
相比之下,v-show 简单许多,元素无论一开始条件如何,始终会被渲染,仅作 CSS 类的切换。
总的来说,v-if 在首次渲染时的切换成本比 v-show 更高。因此当你需要非常频繁切换时 v-show 会更好,而运行时不太会改变的时候 v-if 会更合适。
v-if 和 v-for
- 不推荐同时使用
当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行
列表渲染
v-for
在 v-for 块中可以完整地访问父作用域内的属性。v-for 也支持使用可选的第二个参数,表示当前项的位置索引。
```javascript
data() {
return {
parentMessage: ‘Parent’,
items: [{ message: ‘Foo’ }, { message: ‘Bar’ }]
}
}
{{ parentMessage }} - {{ index }} - {{ item.message }}
- 嵌套
html
{{ item.message }} {{ childItem }}
- of替代in
html
<a name="QRjXV"></a>
### v-for与对象
- 当遍历一个对象时,顺序是依据 Object.keys() 的枚举顺序,由于不同的 JavaScript 引擎可能会有不同的实现,所以可能会导致顺序不一致。
html
data() {
return {
myObject: {
title: ‘如何在 Vue 中渲染列表’,
author: ‘王小明’,
publishedAt: ‘2016-04-10’
}
}
}
{{ index }}. {{ key }}: {{ value }}
<a name="uGY4S"></a>
### v-for
- 可以直接传给 v-for 一个整数值。在这种用例中,将会将该模板基于 1...n 的取值范围重复多次。
html
{{ n }}
<a name="wO5ck"></a>
### <template> 上的 v-for
- 与模板上的 v-if 类似,你也可以在 <template> 标签上使用 v-for 来渲染包含多个元素的一个块。
<a name="rZRW4"></a>
### v-for 与 v-if
html
{{ todo.name }}
{{ todo.name }}
<a name="JW8Fq"></a>
### 通过 key 管理状态
- 为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个项目提供一个唯一的 key 属性:
html
- 当你使用 <template v-for> 时,key 应该被放置在这个 <template> 容器上:
html
{{ todo.name }}
- key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key
<a name="yzwus"></a>
### 组件上使用 v-for
html
- 为了将迭代后的数据传递到组件中,我们还是应该使用 props:
- [待办事项列表的例子](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBUb2RvSXRlbSBmcm9tICcuL1RvZG9JdGVtLnZ1ZSdcbiAgXG5leHBvcnQgZGVmYXVsdCB7XG4gIGNvbXBvbmVudHM6IHsgVG9kb0l0ZW0gfSxcbiAgZGF0YSgpIHtcbiAgICByZXR1cm4ge1xuICAgICAgbmV3VG9kb1RleHQ6ICcnLFxuICAgICAgdG9kb3M6IFtcbiAgICAgICAge1xuICAgICAgICAgIGlkOiAxLFxuICAgICAgICAgIHRpdGxlOiAnRG8gdGhlIGRpc2hlcydcbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIGlkOiAyLFxuICAgICAgICAgIHRpdGxlOiAnVGFrZSBvdXQgdGhlIHRyYXNoJ1xuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgaWQ6IDMsXG4gICAgICAgICAgdGl0bGU6ICdNb3cgdGhlIGxhd24nXG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBuZXh0VG9kb0lkOiA0XG4gICAgfVxuICB9LFxuICBtZXRob2RzOiB7XG4gICAgYWRkTmV3VG9kbygpIHtcbiAgICAgIHRoaXMudG9kb3MucHVzaCh7XG4gICAgICAgIGlkOiB0aGlzLm5leHRUb2RvSWQrKyxcbiAgICAgICAgdGl0bGU6IHRoaXMubmV3VG9kb1RleHRcbiAgICAgIH0pXG4gICAgICB0aGlzLm5ld1RvZG9UZXh0ID0gJydcbiAgICB9XG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG5cdDxmb3JtIHYtb246c3VibWl0LnByZXZlbnQ9XCJhZGROZXdUb2RvXCI+XG4gICAgPGxhYmVsIGZvcj1cIm5ldy10b2RvXCI+QWRkIGEgdG9kbzwvbGFiZWw+XG4gICAgPGlucHV0XG4gICAgICB2LW1vZGVsPVwibmV3VG9kb1RleHRcIlxuICAgICAgaWQ9XCJuZXctdG9kb1wiXG4gICAgICBwbGFjZWhvbGRlcj1cIkUuZy4gRmVlZCB0aGUgY2F0XCJcbiAgICAvPlxuICAgIDxidXR0b24+QWRkPC9idXR0b24+XG4gIDwvZm9ybT5cbiAgPHVsPlxuICAgIDx0b2RvLWl0ZW1cbiAgICAgIHYtZm9yPVwiKHRvZG8sIGluZGV4KSBpbiB0b2Rvc1wiXG4gICAgICA6a2V5PVwidG9kby5pZFwiXG4gICAgICA6dGl0bGU9XCJ0b2RvLnRpdGxlXCJcbiAgICAgIEByZW1vdmU9XCJ0b2Rvcy5zcGxpY2UoaW5kZXgsIDEpXCJcbiAgICA+PC90b2RvLWl0ZW0+XG4gIDwvdWw+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0iLCJUb2RvSXRlbS52dWUiOiI8c2NyaXB0PlxuZXhwb3J0IGRlZmF1bHQge1xuXHRwcm9wczogWyd0aXRsZSddLFxuICBlbWl0czogWydyZW1vdmUnXVxufVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAgPGxpPlxuICAgIHt7IHRpdGxlIH19XG4gICAgPGJ1dHRvbiBAY2xpY2s9XCIkZW1pdCgncmVtb3ZlJylcIj5SZW1vdmU8L2J1dHRvbj5cbiAgPC9saT5cbjwvdGVtcGxhdGU+In0=)
html
<a name="FeMrj"></a>
### 数组变化侦测
Vue 使用代理来观察数组,使其所有更改数组内容的方法都会触发视图更新
<a name="vXUDe"></a>
#### 诱变方法
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
<a name="L43uV"></a>
#### 替换一个数组
- 相对地,也有一些非诱变方法,例如 filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非诱变方法时,我们需要将旧的数组替换为新的:
html
this.items = this.items.filter((item) => item.message.match(/Foo/))
<a name="gRx5c"></a>
### 展示过滤或排序后的结果
- 在计算属性中使用 reverse() 和 sort() 请保持谨慎!这两个方法将改变原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:
javascript
data() {
return {
numbers: [1, 2, 3, 4, 5]
}
},
computed: {
evenNumbers() {
return this.numbers.filter(n => n % 2 === 0)
}
}
{{ n }}
////////
data() {
return {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
}
},
methods: {
even(numbers) {
return numbers.filter(number => number % 2 === 0)
}
}
- {{ n }}
<a name="lCK4J"></a>
## 事件处理
<a name="QqizN"></a>
### 监听事件
你可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件和触发 JavaScript 代码运行。用法可以是 v-on:click="methodName" 或简写为 @click="handler"。
<a name="BJYGG"></a>
### 内联事件处理器
- **内联事件处理器:**事件被触发时执行的内联 JavaScript 语句 (与原生的 onclick attribute 类似)。
javascript
data() {
return {
count: 0
}
}
Add 1
Count is: {{ count }}
<a name="anxag"></a>
### 用方法作事件处理器
- **用方法作为事件处理器:**一个组件的属性名、或对某个方法的访问 (比如 @click="some.method")。
javascript
data() {
return {
name: ‘Vue.js’
}
},
methods: {
greet(event) {
// 方法中的 this
指向的是当前活跃的组件实例
alert(Hello ${this.name}!
)
// event
是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
}
Greet
<a name="pMGCf"></a>
#### 方法还是内联函数的区别
- 模板编译器会通过传给 v-on 的值字符串是否是一个合法的 JavaScript 标识符或属性访问来断定是何种形式的事件处理器。举个例子,foo、foo.bar 和 foo['bar'] 都会被视为方法、而 foo() 和 count++ 都会被视为内联事件处理器。
<a name="IkcLH"></a>
### 在内联处理器中调用方法
html
methods: {
say(message) {
alert(message)
}
}
Say hello
Say bye
<a name="Ptuuq"></a>
### 在内联事件处理器中访问事件参数#
javascript
Submit
warn('Form cannot be submitted yet.', event)">
Submit
methods: {
warn(message, event) {
// 此处访问到了 DOM 原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
}
<a name="Qk0Bc"></a>
### 事件修饰符
html
…
…
…
<a name="hvFVs"></a>
### 按键修饰符
html
Do something
<a name="pKsQa"></a>
#### 按键别名
- .enter
- .tab
- .delete (捕获“Delete”和“Backspace”两个按键)
- .esc
- .space
- .up
- .down
- .left
- .right
- .ctrl
- .alt
- .shift
- .meta
<a name="TMVVP"></a>
### 鼠标按键修饰符
- .left
- .right
- .middle
<a name="IzLtr"></a>
## 表单输入绑定
- v-model 指令帮我们简化下面这个步骤:
- 注意: v-model 会忽略任何表单元素上初始的 value,checked 或 selected attributes。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 侧声明该初始值,使用data 选项。
html
text = event.target.value">
<a name="KDGmj"></a>
### 基本用法
<a name="lLsI3"></a>
#### 文本
html
输入的信息:{{ message }}
<a name="tbB9y"></a>
#### 多行文本
html
多行信息:
{{ message }}
<a name="obRbQ"></a>
#### 复选框
html
/////
export default {
data() {
return {
checkedNames: []
}
}
}
选择的名字有:{{ checkedNames }}
<a name="tVlcH"></a>
#### 单选按钮
html
Picked: {{ picked }}
<a name="WKVxy"></a>
#### 选择器
html
选择的是:{{ selected }}
- 如果 v-model 表达式的初始值不匹配任何一个选择项,<select> 元素会渲染成一个“未选择”的状态。在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发一个 change 事件。因此,我们建议提供一个空值的禁用选项,如上面的例子所示。
<a name="Eq43v"></a>
### 值绑定
- v-model 绑定的值通常是静态的字符串 (或者对复选框是布尔值):
- 有时我们可能希望将该值绑定到当前活动实例上的动态属性,那么可以使用 v-bind 来做到
html
<a name="UBmwO"></a>
#### 复选框
html
<a name="XJtHN"></a>
#### 单选按钮
html
<a name="npaVE"></a>
#### 选择器选项
html
<a name="zKPrf"></a>
### 修饰符
- .lazy
html
- .number
html
让用户输入自动转换为数字
- .trim
- 去掉两端空格
<a name="F7njH"></a>
### 组件上的 v-model
[配合v-model使用](https://staging-cn.vuejs.org/guide/components/events.html#usage-with-v-model)
<a name="Vb3sK"></a>
## 生命周期钩子

<a name="p0l4l"></a>
## 侦听器
javascript
export default {
data() {
return {
question: ‘’,
answer: ‘问句通常都会带一个问号。;-)’
}
},
watch: {
// 只要问题发生变化,这个函数就会运行
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf(‘?’) > -1) {
this.getAnswer()
}
}
},
methods: {
async getAnswer() {
this.answer = ‘思考中…’
try {
const res = await fetch(‘https://yesno.wtf/api‘)
this.answer = (await res.json()).answer
} catch (e) {
this.answer = ‘出错了!无法访问该 API。’ + error
}
}
}
}
提一个 Yes/No 的问题:
{{ answer }}
<a name="srOQf"></a>
### 深层侦听器
javascript
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// 注意:在深层次变更中,
// 只要对象本身没有被替换,
// 那么newValue
这里和 oldValue
就是相同的
},
deep: true
}
}
}
<a name="blloR"></a>
### 积极侦听
javascript
export default {
// …
watch: {
question: {
handler(newQuestion) {
// 在组件实例创建时会立即调用
},
// 强制积极执行回调
immediate: true
}
}
// …
}
<a name="aUYhE"></a>
### 副作用刷新时机
- 如果你想于 Vue 更新之后,在侦听器回调中访问 DOM,你需要指明 flush: 'post' 选项:
javascript
export default {
// …
watch: {
key: {
handler() {},
flush: ‘post’
}
}
}
<a name="kuJPw"></a>
### this.$watch()
- 我们也可以使用组件实例的 [$watch()方法](https://staging-cn.vuejs.org/api/component-instance.html#watch) 来命令式地创建一个侦听器:
javascript
export default {
created() {
this.$watch(‘question’, (newQuestion) => {
// …
})
}
}
<a name="YviQ2"></a>
### 停止侦听器
- 由 watch 选项和 $watch() 实例方法声明的侦听器会在宿主组件卸载时自动停止,因此大多数场景下你无需关心要怎么操作来停止它。
- 少数情况下
javascript
const unwatch = this.$watch(‘foo’, callback)
// …当该侦听器不再需要时
unwatch()
<a name="WW7fV"></a>
## 模板 ref
- 在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute:
- 挂载结束后 ref 都会被暴露在 this.$refs 之上:
- ,你只可以**在组件挂载后**才能访问 ref
html
<a name="bslLS"></a>
### v-for中的ref
- ref 数组**不能**保证与源数组相同的顺序。
html
-
{{ item }}
<a name="bZsjR"></a>
### 函数型 ref
html
<a name="MIiuc"></a>
### 组件上的ref
- ref 也可以被用在一个子组件上。此时 ref 中引用的是组件实例:
html
- 如果一个子组件使用的是选项式 API ,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。
- 大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。
- 下面例子中父组件通过模板 ref 访问到子组件实例后,仅能访问 publicData 和 publicMethod。
javascript
export default {
expose: [‘publicData’, ‘publicMethod’],
data() {
return {
publicData: ‘foo’,
privateData: ‘bar’
}
},
methods: {
publicMethod() {
/ … /
},
privateMethod() {
/ … /
}
}
}
<a name="D2lE5"></a>
## 组件基础
<a name="H9IHa"></a>
### 定义一个组件
- 单文件组件 SFC , .vue文件
javascript
点击了 {{ count }} 次
- 不使用构建时
javascript
export default {
data() {
return {
count: 0
}
},
template: <button @click="count++">
You clicked me {{ count }} times.
</button>
}
- 上面的例子中定义了一个组件,并在一个 .js 文件里默认导出了它自己,但你也可以通过具名导出在一个文件中导出多个组件。
<a name="zI3db"></a>
### 使用组件
- 要使用一个子组件,我们需要在父组件中导入它。
- 若要将导入的组件暴露给模板,我们需要在 components 选项上[注册](https://staging-cn.vuejs.org/guide/components/registration.html)它。这个组件将会以其注册时的名字作为模板中的标签名。
- 当然,你也可以全局地注册一个组件,使得它在当前应用中的任何组件上都可以使用,而不需要额外再导入。
- 组件可以被重用任意多次,每当你使用一个组件,就创建了一个新的**实例**,互不影响。
javascript
这里是一个子组件!
<a name="tA5DC"></a>
### 传递 props
javascript
{{ title }}
export default {
// …
data() {
return {
posts: [
{ id: 1, title: ‘My journey with Vue’ },
{ id: 2, title: ‘Blogging with Vue’ },
{ id: 3, title: ‘Why Vue is so fun’ }
]
}
}
}
<a name="jnrxW"></a>
### 监听事件
- [emit使用](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBCbG9nUG9zdCBmcm9tICcuL0Jsb2dQb3N0LnZ1ZSdcbiAgXG5leHBvcnQgZGVmYXVsdCB7XG4gIGNvbXBvbmVudHM6IHtcbiAgICBCbG9nUG9zdFxuICB9LFxuICBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBwb3N0czogW1xuICAgICAgICB7IGlkOiAxLCB0aXRsZTogJ015IGpvdXJuZXkgd2l0aCBWdWUnIH0sXG4gICAgICAgIHsgaWQ6IDIsIHRpdGxlOiAnQmxvZ2dpbmcgd2l0aCBWdWUnIH0sXG4gICAgICAgIHsgaWQ6IDMsIHRpdGxlOiAnV2h5IFZ1ZSBpcyBzbyBmdW4nIH1cbiAgICAgIF0sXG4gICAgICBwb3N0Rm9udFNpemU6IDFcbiAgICB9XG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxkaXYgOnN0eWxlPVwieyBmb250U2l6ZTogcG9zdEZvbnRTaXplICsgJ2VtJyB9XCI+XG4gICAgPEJsb2dQb3N0XG4gICAgICB2LWZvcj1cInBvc3QgaW4gcG9zdHNcIlxuICAgICAgOmtleT1cInBvc3QuaWRcIlxuICAgICAgOnRpdGxlPVwicG9zdC50aXRsZVwiXG4gICAgICBAZW5sYXJnZS10ZXh0PVwicG9zdEZvbnRTaXplICs9IDAuMVwiXG4gICAgPjwvQmxvZ1Bvc3Q+XG4gIDwvZGl2PlxuPC90ZW1wbGF0ZT4iLCJpbXBvcnQtbWFwLmpzb24iOiJ7XG4gIFwiaW1wb3J0c1wiOiB7XG4gICAgXCJ2dWVcIjogXCJodHRwczovL3NmYy52dWVqcy5vcmcvdnVlLnJ1bnRpbWUuZXNtLWJyb3dzZXIuanNcIlxuICB9XG59IiwiQmxvZ1Bvc3QudnVlIjoiPHNjcmlwdD5cbmV4cG9ydCBkZWZhdWx0IHtcbiAgcHJvcHM6IFsndGl0bGUnXSxcbiAgZW1pdHM6IFsnZW5sYXJnZS10ZXh0J11cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxkaXYgY2xhc3M9XCJibG9nLXBvc3RcIj5cblx0ICA8aDQ+e3sgdGl0bGUgfX08L2g0PlxuXHQgIDxidXR0b24gQGNsaWNrPVwiJGVtaXQoJ2VubGFyZ2UtdGV4dCcpXCI+RW5sYXJnZSB0ZXh0PC9idXR0b24+XG4gIDwvZGl2PlxuPC90ZW1wbGF0ZT4ifQ==)
<a name="eZSwN"></a>
### 通过插槽来分配内容
- [slot使用](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBBbGVydEJveCBmcm9tICcuL0FsZXJ0Qm94LnZ1ZSdcbiAgXG5leHBvcnQgZGVmYXVsdCB7XG4gIGNvbXBvbmVudHM6IHsgQWxlcnRCb3ggfVxufVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cblx0PEFsZXJ0Qm94PlxuICBcdFNvbWV0aGluZyBiYWQgaGFwcGVuZWQuXG5cdDwvQWxlcnRCb3g+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0iLCJBbGVydEJveC52dWUiOiI8dGVtcGxhdGU+XG4gIDxkaXYgY2xhc3M9XCJhbGVydC1ib3hcIj5cbiAgICA8c3Ryb25nPkVycm9yITwvc3Ryb25nPlxuICAgIDxici8+XG4gICAgPHNsb3QgLz5cbiAgPC9kaXY+XG48L3RlbXBsYXRlPlxuXG48c3R5bGUgc2NvcGVkPlxuLmFsZXJ0LWJveCB7XG4gIGNvbG9yOiAjNjY2O1xuICBib3JkZXI6IDFweCBzb2xpZCByZWQ7XG4gIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgcGFkZGluZzogMjBweDtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2Y4ZjhmODtcbn1cbiAgXG5zdHJvbmcge1xuXHRjb2xvcjogcmVkOyAgICBcbn1cbjwvc3R5bGU+In0=)
<a name="o6UXV"></a>
### 动态组件
- [动态组件](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBIb21lIGZyb20gJy4vSG9tZS52dWUnXG5pbXBvcnQgUG9zdHMgZnJvbSAnLi9Qb3N0cy52dWUnXG5pbXBvcnQgQXJjaGl2ZSBmcm9tICcuL0FyY2hpdmUudnVlJ1xuICBcbmV4cG9ydCBkZWZhdWx0IHtcbiAgY29tcG9uZW50czoge1xuICAgIEhvbWUsXG4gICAgUG9zdHMsXG4gICAgQXJjaGl2ZVxuICB9LFxuICBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBjdXJyZW50VGFiOiAnSG9tZScsXG4gICAgICB0YWJzOiBbJ0hvbWUnLCAnUG9zdHMnLCAnQXJjaGl2ZSddXG4gICAgfVxuICB9XG59XG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8ZGl2IGNsYXNzPVwiZGVtb1wiPlxuICAgIDxidXR0b25cbiAgICAgICB2LWZvcj1cInRhYiBpbiB0YWJzXCJcbiAgICAgICA6a2V5PVwidGFiXCJcbiAgICAgICA6Y2xhc3M9XCJbJ3RhYi1idXR0b24nLCB7IGFjdGl2ZTogY3VycmVudFRhYiA9PT0gdGFiIH1dXCJcbiAgICAgICBAY2xpY2s9XCJjdXJyZW50VGFiID0gdGFiXCJcbiAgICAgPlxuICAgICAge3sgdGFiIH19XG4gICAgPC9idXR0b24+XG5cdCAgPGNvbXBvbmVudCA6aXM9XCJjdXJyZW50VGFiXCIgY2xhc3M9XCJ0YWJcIj48L2NvbXBvbmVudD5cbiAgPC9kaXY+XG48L3RlbXBsYXRlPlxuXG48c3R5bGU+XG4uZGVtbyB7XG4gIGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmO1xuICBib3JkZXI6IDFweCBzb2xpZCAjZWVlO1xuICBib3JkZXItcmFkaXVzOiAycHg7XG4gIHBhZGRpbmc6IDIwcHggMzBweDtcbiAgbWFyZ2luLXRvcDogMWVtO1xuICBtYXJnaW4tYm90dG9tOiA0MHB4O1xuICB1c2VyLXNlbGVjdDogbm9uZTtcbiAgb3ZlcmZsb3cteDogYXV0bztcbn1cblxuLnRhYi1idXR0b24ge1xuICBwYWRkaW5nOiA2cHggMTBweDtcbiAgYm9yZGVyLXRvcC1sZWZ0LXJhZGl1czogM3B4O1xuICBib3JkZXItdG9wLXJpZ2h0LXJhZGl1czogM3B4O1xuICBib3JkZXI6IDFweCBzb2xpZCAjY2NjO1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIGJhY2tncm91bmQ6ICNmMGYwZjA7XG4gIG1hcmdpbi1ib3R0b206IC0xcHg7XG4gIG1hcmdpbi1yaWdodDogLTFweDtcbn1cbi50YWItYnV0dG9uOmhvdmVyIHtcbiAgYmFja2dyb3VuZDogI2UwZTBlMDtcbn1cbi50YWItYnV0dG9uLmFjdGl2ZSB7XG4gIGJhY2tncm91bmQ6ICNlMGUwZTA7XG59XG4udGFiIHtcbiAgYm9yZGVyOiAxcHggc29saWQgI2NjYztcbiAgcGFkZGluZzogMTBweDtcbn1cbjwvc3R5bGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIkhvbWUudnVlIjoiPHRlbXBsYXRlPlxuICA8ZGl2IGNsYXNzPVwidGFiXCI+XG4gICAgSG9tZSBjb21wb25lbnRcbiAgPC9kaXY+XG48L3RlbXBsYXRlPiIsIlBvc3RzLnZ1ZSI6Ijx0ZW1wbGF0ZT5cbiAgPGRpdiBjbGFzcz1cInRhYlwiPlxuICAgIFBvc3RzIGNvbXBvbmVudFxuICA8L2Rpdj5cbjwvdGVtcGxhdGU+IiwiQXJjaGl2ZS52dWUiOiI8dGVtcGxhdGU+XG4gIDxkaXYgY2xhc3M9XCJ0YWJcIj5cbiAgICBBcmNoaXZlIGNvbXBvbmVudFxuICA8L2Rpdj5cbjwvdGVtcGxhdGU+In0=)
```javascript
<component :is="currentTab"></component>
- 在上面的例子中,被传给 :is 的值可以是以下几种:
- 被注册的组件名
- 导入的组件对象
- 你也可以使用 is attribute 来创建一般的 HTML 元素。
- 当使用
来在多个组件间作切换时,组件会在被切换掉后卸载。我们可以通过 组件强制不活跃的组件仍然保持“存活”的状态。
DOM 模板解析注意事项
大小写区分
- HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式
``javascript
// JavaScript 中的 camelCase
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost']
template:
{{ postTitle }}
`
}
<a name="uG084"></a>
#### 闭合标签
- 在 DOM 模板中,我们必须显式地写出关闭标签
<a name="R3nvq"></a>
#### 元素位置限制
- 某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul>,<ol>,<table> 和 <select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li>,<tr> 和 <option>。
- s
```html
<table>
<tr is="vue:blog-post-row"></tr>
</table>
深入组件
组件注册
全局注册
import { createApp } from 'vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',
// 组件的实现
{
/* ... */
}
)
//*******
import MyComponent from './App.vue`
app.component('MyComponent', MyComponent)
局部注册
- 局部注册组件在后代组件中并不可用。
```javascript
<a name="SuBCi"></a>
### 组件名格式
- 在整个指引中,我们都使用 PascalCase 作为组件名字的注册格式,这是因为:
1. PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 作导入和注册组件都很容易,同时 IDE 也能提供较好的的自动补全。
2. <PascalCase /> 使得 Vue 组件在模板中相较于一个一般的原生 HTML 元素更明显。同时也将 Vue 组件和自定义组件区分开来 (Web components)。
- Vue 支持将那些用 PascalCase 注册的组件转换为 kebab-case 形式的标签名。这意味着一个以 MyComponent 为名注册的组件,可以在模板中既可以使用 <MyComponent> 也可以使用 <my-component>
<a name="ZElb2"></a>
## Props
<a name="h1lie"></a>
### 声明
javascript
export default {
props: [‘foo’],
created() {
// props 会暴露到 this
上
console.log(this.foo)
}
}
export default {
props: {
title: String,
likes: Number
}
}
<a name="x6C9n"></a>
### 传递 props 的细节
- 如果 prop 的名字很长,应使用 camelCase 形式
<a name="KyB64"></a>
#### 静态 vs. 动态 Props
javascript
<a name="nUKFc"></a>
#### 传递不同的值类型
javascript
<a name="bav9A"></a>
#### 使用一个对象绑定多个 prop
javascript
export default {
data() {
return {
post: {
id: 1,
title: ‘我的 Vue 学习之旅’
}
}
}
}
//等价于
<a name="uQC00"></a>
### 单向数据流
- 所有的 prop 都遵循着单向绑定原则,prop 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改了父组件的状态,不然应用的数据流就会变得难以理解了。
<a name="UFxTk"></a>
#### 更改对象 / 数组类型的 prop
- 当对象或数组作为 prop 被传入时,虽然子组件无法更改 prop 绑定,但仍然**可以**更改对象或数组内部的值
<a name="Km8ew"></a>
## Prop 校验
javascript
export default {
props: {
// 基础类型检查(给出 null
和 undefined
值则会跳过任何类型校验)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props
// 作为参数
default(rawProps) {
// default 函数接收传入的原始 props 作为参数
return { message: ‘hello’ }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return [‘success’, ‘warning’, ‘danger’].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return ‘Default function’
}
}
}
}
<a name="vU3h9"></a>
#### 运行时类型检查
<a name="a5d5m"></a>
### Boolean 类型转换
javascript
export default {
props: {
disabled: Boolean
}
}
```html
<!-- equivalent of passing :disabled="true" -->
<MyComponent disabled />
<!-- equivalent of passing :disabled="false" -->
<MyComponent />
## 组件事件
### 抛出与监听事件
- 注意我们抛出的是一个名字是 camelCase 形式的事件,但可以在父组件中使用 kebab-case 形式来监听。
html
<button @click="$emit('someEvent')">点击这里抛出事件</button>
<MyComponent @some-event="callback" />
<MyComponent @some-event.once="callback" />
### 事件参数
html
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
<MyButton @increase-by="(n) => count += n" />
<MyButton @increase-by="increaseCount" />
methods: {
increaseCount(n) {
this.count += n
}
}
### 声明抛出的事件
javascript
export default {
emits: ['inFocus', 'submit']
}
- 如果一个原生事件的名字 (例如 click) 被定义在 emits 选项中,则监听器只会监听组件发出的 click 事件而不会再响应原生的 click 事件。
### 事件校验
javascript
export default {
emits: {
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
}
#### 配合 v-model
- v-model
#### 处理 v-model 修饰符
- 在 Playground 中尝试一下
### 透传Attribute
### Atrribute继承
- 如果一个子组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并。
- 同样的规则也适用于 v-on 事件监听器:
### 禁用 Attribute 继承
我们想要所有像 class 和 v-on 监听器这样的透传 attribute 都应用在内部的 上而不是外层的 上。我们可以通过设定 inheritAttrs: false 和使用 v-bind=”$attrs” 来实现:
javascript
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">点击此处</button>
</div>
### 多根节点的 Attribute 继承
javascript
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
### javascripts中访问透传 Attributes
javascript
export default {
created() {
console.log(this.$attrs)
}
}
## 插槽
### 插槽内容与插口
- 插槽内容不仅仅局限于文本。它也可以是任意合法的模板内容,例如我们可以传入一些元素,甚至是组件:
- 插槽内容可以访问到父组件的数据,因为插槽内容本身也是在父组件模板的一部分
### 默认内容
javascript
<button type="submit">
<slot>
提交 <!-- 默认内容 -->
</slot>
</button>
### 具名插槽
### 动态插槽名
```javascript
…
…
<a name="Yw1Nx"></a>
### 作用域插槽
- 然而在某些场景下插槽的内容可能想要同时利用父组件域内和子组件域内的数据。要做到这一点,我们需要让子组件将一部分数据在渲染时提供给插槽。
```html
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComonent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
<MyComponent>
无渲染组件
export default {
provide: {
message: 'hello!'
}
}
//下面做法 请注意这不会使注入保持响应性
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
// 使用函数的形式,可以访问到 `this`
return {
message: this.message
}
}
}
//-----------
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
inject
export default {
inject: ['message'],
created() {
console.log(this.message) // injected value
}
}
export default {
inject: ['message'],
data() {
return {
// 基于注入值的初始数据
fullMessage: this.message
}
}
}
注入的别名
export default {
inject: {
/* 局部注入名 */ localMessage: {
from: /* 原注入名 */ 'message'
}
}
}
注入的默认值
export default {
// 当声明注入的默认值时
// 必须使用对象形式
inject: {
message: {
from: 'message', // 当与原注入名同名时,这个属性是可选的
default: 'default value'
},
user: {
// 对于非基础类型数据,如果创建开销比较大,或是需要确保每个组件实例
// 需要独立数据的,请使用工厂函数
default: () => ({ name: 'John' })
}
}
}
配合响应性
- 为保证注入方和供给端的响应性链接,我们需要使用 computed () 函数提供一个计算属性:
- 带有响应性的供给 + 注入完整示例
```javascript
import { computed } from ‘vue’
export default {
data() {
return {
message: ‘hello!’
}
},
provide() {
return {
// 显式提供一个计算属性
message: computed(() => this.message)
}
}
}
<a name="ebdCS"></a>
### 使用 Symbol 作注入名
- 但如果你正在构建大型的应用程序,包含非常多的依赖供给,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。
```javascript
// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { myInjectionKey } from './keys.js'
export default {
provide() {
return {
[myInjectionKey]: {
/* 要供给的数据 */
}
}
}
}
// 注入方组件
import { myInjectionKey } from './keys.js'
export default {
inject: {
injected: { from: myInjectionKey }
}
}
异步组件
基础使用
- 在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载。
```javascript
import { defineAsyncComponent } from ‘vue’
export default {
// …
components: {
AsyncComponent: defineAsyncComponent(() =>
import(‘./components/AsyncComponent.vue’)
)
}
}
<a name="s6sAe"></a>
### 加载与错误状态
- 异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent() 也支持在选项中处理这些状态:
```javascript
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载完成的组件前的延迟,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超过了该时间
// 也会显示报错组件
timeout: 3000
})
搭配 Suspense 使用
可重用性
可组合函数
- 一个“可组合函数”是一个利用组合式 API 来封装可重用有状态逻辑的函数。
鼠标跟踪器示例
异步状态示例
升级版的useFetch()
```javascript
// fetch.js
import { ref, isRef, unref, watchEffect } from ‘vue’
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
function doFetch() {
// 在请求之前重设状态…
data.value = null
error.value = null
// unref() 解套可能为 ref 的值
fetch(unref(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
if (isRef(url)) {
// 若输入的 URL 是一个 ref,那么启动一个响应式的请求
watchEffect(doFetch)
} else {
// 否则只请求一次
// 避免监听器的额外开销
doFetch()
}
return { data, error }
}
<a name="EilIi"></a>
### 约定和最佳实践
<a name="uEIeG"></a>
#### 命名
- 约定将可组合函数命名为以“use”开头的函数。
<a name="g1gJb"></a>
#### 输入参数
- 如果你正在编写一个提供给其他开发者使用的可组合函数,最好处理输入参数是 ref 而不是原始值。[unref()](https://staging-cn.vuejs.org/api/reactivity-utilities.html#unref) 工具函数在此时带来极大方便:
```javascript
import { unref } from 'vue'
function useFeature(maybeRef) {
// 若 maybeRef 的确是一个 ref,它的 .value 会被返回
// 否则返回其本身
const value = unref(maybeRef)
}
返回值
- 约定推荐始终从可组合函数中返回一个包含 ref 的对象,这样在对象解构时可以保持响应性:
- 从可组合函数返回一个响应式对象会导致对象解构时丢失与可组合函数内状态的响应性连接。
果你更想使用可组合函数返回对象的属性,你可以将该返回对象用 reactive() 包裹,因而其中的 ref 在访问时会自动解套
副作用
在可组合函数中的确可以执行副作用 (例如:添加 DOM 事件监听器或者请求数据),但请注意以下规则:
- 如果你在一个应用程序中使用了服务器端渲染 (SSR),请确保在后置加载的声明钩子上执行 DOM 相关的副作用,例如:onMounted()。这些钩子仅会在浏览器中使用,因此可以确保能访问到 DOM。
- 确保在 onUnmounted() 时清理副作用。举个例子,如果一个可组合函数设置了一个事件监听器,它就应该在 onUnmounted() 中被移除 (就像我们在 useMouse() 示例中看到的一样)。当然也可以使用一个可组合函数来自动帮你做这些事,例如之前的 useEventListener() 示例。
使用限制
- 可组合函数应该在
v-show
- 不同之处在于 v-show 会在 DOM 渲染中保留该节点;v-show 仅切换了该元素的 display CSS 属性。
v-show 不支持在 元素上使用,也没有 v-else 来配合。
v-if 是“真实的”按条件渲染,因为它确保了条件区块内的事件监听器和子组件都会在切换时被销毁与重建。 当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行
在 v-for 块中可以完整地访问父作用域内的属性。v-for 也支持使用可选的第二个参数,表示当前项的位置索引。
```javascript
data() {
return {
parentMessage: ‘Parent’,
items: [{ message: ‘Foo’ }, { message: ‘Bar’ }]
}
} Count is: {{ count }} 输入的信息:{{ message }} {{ message }}
提一个 Yes/No 的问题:
{{ answer }} export default {
// …
data() {
return {
posts: [
{ id: 1, title: ‘My journey with Vue’ },
{ id: 2, title: ‘Blogging with Vue’ },
{ id: 3, title: ‘Why Vue is so fun’ }
]
}
}
}
…
export default {
data() {
return {
message: ‘hello!’
}
},
provide() {
return {
// 显式提供一个计算属性
message: computed(() => this.message)
}
}
} export default {
// …
components: {
AsyncComponent: defineAsyncComponent(() =>
import(‘./components/AsyncComponent.vue’)
)
}
} export function useFetch(url) {
const data = ref(null)
const error = ref(null) function doFetch() {
// 在请求之前重设状态…
data.value = null
error.value = null
// unref() 解套可能为 ref 的值
fetch(unref(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
} if (isRef(url)) {
// 若输入的 URL 是一个 ref,那么启动一个响应式的请求
watchEffect(doFetch)
} else {
// 否则只请求一次
// 避免监听器的额外开销
doFetch()
} return { data, error }
} 果你更想使用可组合函数返回对象的属性,你可以将该返回对象用 reactive() 包裹,因而其中的 ref 在访问时会自动解套
在可组合函数中的确可以执行副作用 (例如:添加 DOM 事件监听器或者请求数据),但请注意以下规则:v-if vs v-show
v-if 是懒加载的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块会直到条件首次变为 true 时才渲染。
相比之下,v-show 简单许多,元素无论一开始条件如何,始终会被渲染,仅作 CSS 类的切换。
总的来说,v-if 在首次渲染时的切换成本比 v-show 更高。因此当你需要非常频繁切换时 v-show 会更好,而运行时不太会改变的时候 v-if 会更合适。
v-if 和 v-for
列表渲染
v-for
- 嵌套
html
- of替代in
html
<a name="QRjXV"></a>
### v-for与对象
- 当遍历一个对象时,顺序是依据 Object.keys() 的枚举顺序,由于不同的 JavaScript 引擎可能会有不同的实现,所以可能会导致顺序不一致。
html
data() {
return {
myObject: {
title: ‘如何在 Vue 中渲染列表’,
author: ‘王小明’,
publishedAt: ‘2016-04-10’
}
}
}
<a name="uGY4S"></a>
### v-for
- 可以直接传给 v-for 一个整数值。在这种用例中,将会将该模板基于 1...n 的取值范围重复多次。
html
{{ n }}
<a name="wO5ck"></a>
### <template> 上的 v-for
- 与模板上的 v-if 类似,你也可以在 <template> 标签上使用 v-for 来渲染包含多个元素的一个块。
<a name="rZRW4"></a>
### v-for 与 v-if
html
<a name="JW8Fq"></a>
### 通过 key 管理状态
- 为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个项目提供一个唯一的 key 属性:
html
- 当你使用 <template v-for> 时,key 应该被放置在这个 <template> 容器上:
html
- key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key
<a name="yzwus"></a>
### 组件上使用 v-for
html
- 为了将迭代后的数据传递到组件中,我们还是应该使用 props:
- [待办事项列表的例子](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBUb2RvSXRlbSBmcm9tICcuL1RvZG9JdGVtLnZ1ZSdcbiAgXG5leHBvcnQgZGVmYXVsdCB7XG4gIGNvbXBvbmVudHM6IHsgVG9kb0l0ZW0gfSxcbiAgZGF0YSgpIHtcbiAgICByZXR1cm4ge1xuICAgICAgbmV3VG9kb1RleHQ6ICcnLFxuICAgICAgdG9kb3M6IFtcbiAgICAgICAge1xuICAgICAgICAgIGlkOiAxLFxuICAgICAgICAgIHRpdGxlOiAnRG8gdGhlIGRpc2hlcydcbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIGlkOiAyLFxuICAgICAgICAgIHRpdGxlOiAnVGFrZSBvdXQgdGhlIHRyYXNoJ1xuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgaWQ6IDMsXG4gICAgICAgICAgdGl0bGU6ICdNb3cgdGhlIGxhd24nXG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBuZXh0VG9kb0lkOiA0XG4gICAgfVxuICB9LFxuICBtZXRob2RzOiB7XG4gICAgYWRkTmV3VG9kbygpIHtcbiAgICAgIHRoaXMudG9kb3MucHVzaCh7XG4gICAgICAgIGlkOiB0aGlzLm5leHRUb2RvSWQrKyxcbiAgICAgICAgdGl0bGU6IHRoaXMubmV3VG9kb1RleHRcbiAgICAgIH0pXG4gICAgICB0aGlzLm5ld1RvZG9UZXh0ID0gJydcbiAgICB9XG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG5cdDxmb3JtIHYtb246c3VibWl0LnByZXZlbnQ9XCJhZGROZXdUb2RvXCI+XG4gICAgPGxhYmVsIGZvcj1cIm5ldy10b2RvXCI+QWRkIGEgdG9kbzwvbGFiZWw+XG4gICAgPGlucHV0XG4gICAgICB2LW1vZGVsPVwibmV3VG9kb1RleHRcIlxuICAgICAgaWQ9XCJuZXctdG9kb1wiXG4gICAgICBwbGFjZWhvbGRlcj1cIkUuZy4gRmVlZCB0aGUgY2F0XCJcbiAgICAvPlxuICAgIDxidXR0b24+QWRkPC9idXR0b24+XG4gIDwvZm9ybT5cbiAgPHVsPlxuICAgIDx0b2RvLWl0ZW1cbiAgICAgIHYtZm9yPVwiKHRvZG8sIGluZGV4KSBpbiB0b2Rvc1wiXG4gICAgICA6a2V5PVwidG9kby5pZFwiXG4gICAgICA6dGl0bGU9XCJ0b2RvLnRpdGxlXCJcbiAgICAgIEByZW1vdmU9XCJ0b2Rvcy5zcGxpY2UoaW5kZXgsIDEpXCJcbiAgICA+PC90b2RvLWl0ZW0+XG4gIDwvdWw+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0iLCJUb2RvSXRlbS52dWUiOiI8c2NyaXB0PlxuZXhwb3J0IGRlZmF1bHQge1xuXHRwcm9wczogWyd0aXRsZSddLFxuICBlbWl0czogWydyZW1vdmUnXVxufVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAgPGxpPlxuICAgIHt7IHRpdGxlIH19XG4gICAgPGJ1dHRvbiBAY2xpY2s9XCIkZW1pdCgncmVtb3ZlJylcIj5SZW1vdmU8L2J1dHRvbj5cbiAgPC9saT5cbjwvdGVtcGxhdGU+In0=)
html
<a name="FeMrj"></a>
### 数组变化侦测
Vue 使用代理来观察数组,使其所有更改数组内容的方法都会触发视图更新
<a name="vXUDe"></a>
#### 诱变方法
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
<a name="L43uV"></a>
#### 替换一个数组
- 相对地,也有一些非诱变方法,例如 filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非诱变方法时,我们需要将旧的数组替换为新的:
html
this.items = this.items.filter((item) => item.message.match(/Foo/))
<a name="gRx5c"></a>
### 展示过滤或排序后的结果
- 在计算属性中使用 reverse() 和 sort() 请保持谨慎!这两个方法将改变原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:
javascript
data() {
return {
numbers: [1, 2, 3, 4, 5]
}
},
computed: {
evenNumbers() {
return this.numbers.filter(n => n % 2 === 0)
}
}
<a name="lCK4J"></a>
## 事件处理
<a name="QqizN"></a>
### 监听事件
你可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件和触发 JavaScript 代码运行。用法可以是 v-on:click="methodName" 或简写为 @click="handler"。
<a name="BJYGG"></a>
### 内联事件处理器
- **内联事件处理器:**事件被触发时执行的内联 JavaScript 语句 (与原生的 onclick attribute 类似)。
javascript
data() {
return {
count: 0
}
}
<a name="anxag"></a>
### 用方法作事件处理器
- **用方法作为事件处理器:**一个组件的属性名、或对某个方法的访问 (比如 @click="some.method")。
javascript
data() {
return {
name: ‘Vue.js’
}
},
methods: {
greet(event) {
// 方法中的 this
指向的是当前活跃的组件实例
alert(Hello ${this.name}!
)
// event
是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
}
<a name="pMGCf"></a>
#### 方法还是内联函数的区别
- 模板编译器会通过传给 v-on 的值字符串是否是一个合法的 JavaScript 标识符或属性访问来断定是何种形式的事件处理器。举个例子,foo、foo.bar 和 foo['bar'] 都会被视为方法、而 foo() 和 count++ 都会被视为内联事件处理器。
<a name="IkcLH"></a>
### 在内联处理器中调用方法
html
methods: {
say(message) {
alert(message)
}
}
<a name="Ptuuq"></a>
### 在内联事件处理器中访问事件参数#
javascript
此处访问到了 DOM 原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
}
<a name="Qk0Bc"></a>
### 事件修饰符
html
<a name="hvFVs"></a>
### 按键修饰符
html
<a name="pKsQa"></a>
#### 按键别名
- .enter
- .tab
- .delete (捕获“Delete”和“Backspace”两个按键)
- .esc
- .space
- .up
- .down
- .left
- .right
- .ctrl
- .alt
- .shift
- .meta
<a name="TMVVP"></a>
### 鼠标按键修饰符
- .left
- .right
- .middle
<a name="IzLtr"></a>
## 表单输入绑定
- v-model 指令帮我们简化下面这个步骤:
- 注意: v-model 会忽略任何表单元素上初始的 value,checked 或 selected attributes。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 侧声明该初始值,使用data 选项。
html
text = event.target.value">
<a name="KDGmj"></a>
### 基本用法
<a name="lLsI3"></a>
#### 文本
html
<a name="tbB9y"></a>
#### 多行文本
html
多行信息:
<a name="obRbQ"></a>
#### 复选框
html
/////
export default {
data() {
return {
checkedNames: []
}
}
}
<a name="tVlcH"></a>
#### 单选按钮
html
<a name="WKVxy"></a>
#### 选择器
html
- 如果 v-model 表达式的初始值不匹配任何一个选择项,<select> 元素会渲染成一个“未选择”的状态。在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发一个 change 事件。因此,我们建议提供一个空值的禁用选项,如上面的例子所示。
<a name="Eq43v"></a>
### 值绑定
- v-model 绑定的值通常是静态的字符串 (或者对复选框是布尔值):
- 有时我们可能希望将该值绑定到当前活动实例上的动态属性,那么可以使用 v-bind 来做到
html
<a name="UBmwO"></a>
#### 复选框
html
<a name="XJtHN"></a>
#### 单选按钮
html
<a name="npaVE"></a>
#### 选择器选项
html
<a name="zKPrf"></a>
### 修饰符
- .lazy
html
- .number
html
让用户输入自动转换为数字
- .trim
- 去掉两端空格
<a name="F7njH"></a>
### 组件上的 v-model
[配合v-model使用](https://staging-cn.vuejs.org/guide/components/events.html#usage-with-v-model)
<a name="Vb3sK"></a>
## 生命周期钩子

<a name="p0l4l"></a>
## 侦听器
javascript
export default {
data() {
return {
question: ‘’,
answer: ‘问句通常都会带一个问号。;-)’
}
},
watch: {
// 只要问题发生变化,这个函数就会运行
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf(‘?’) > -1) {
this.getAnswer()
}
}
},
methods: {
async getAnswer() {
this.answer = ‘思考中…’
try {
const res = await fetch(‘https://yesno.wtf/api‘)
this.answer = (await res.json()).answer
} catch (e) {
this.answer = ‘出错了!无法访问该 API。’ + error
}
}
}
}
<a name="srOQf"></a>
### 深层侦听器
javascript
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// 注意:在深层次变更中,
// 只要对象本身没有被替换,
// 那么newValue
这里和 oldValue
就是相同的
},
deep: true
}
}
}
<a name="blloR"></a>
### 积极侦听
javascript
export default {
// …
watch: {
question: {
handler(newQuestion) {
// 在组件实例创建时会立即调用
},
// 强制积极执行回调
immediate: true
}
}
// …
}
<a name="aUYhE"></a>
### 副作用刷新时机
- 如果你想于 Vue 更新之后,在侦听器回调中访问 DOM,你需要指明 flush: 'post' 选项:
javascript
export default {
// …
watch: {
key: {
handler() {},
flush: ‘post’
}
}
}
<a name="kuJPw"></a>
### this.$watch()
- 我们也可以使用组件实例的 [$watch()方法](https://staging-cn.vuejs.org/api/component-instance.html#watch) 来命令式地创建一个侦听器:
javascript
export default {
created() {
this.$watch(‘question’, (newQuestion) => {
// …
})
}
}
<a name="YviQ2"></a>
### 停止侦听器
- 由 watch 选项和 $watch() 实例方法声明的侦听器会在宿主组件卸载时自动停止,因此大多数场景下你无需关心要怎么操作来停止它。
- 少数情况下
javascript
const unwatch = this.$watch(‘foo’, callback)
// …当该侦听器不再需要时
unwatch()
<a name="WW7fV"></a>
## 模板 ref
- 在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute:
- 挂载结束后 ref 都会被暴露在 this.$refs 之上:
- ,你只可以**在组件挂载后**才能访问 ref
html
<a name="bslLS"></a>
### v-for中的ref
- ref 数组**不能**保证与源数组相同的顺序。
html
<a name="bZsjR"></a>
### 函数型 ref
html
<a name="MIiuc"></a>
### 组件上的ref
- ref 也可以被用在一个子组件上。此时 ref 中引用的是组件实例:
html
- 如果一个子组件使用的是选项式 API ,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。
- 大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。
- 下面例子中父组件通过模板 ref 访问到子组件实例后,仅能访问 publicData 和 publicMethod。
javascript
export default {
expose: [‘publicData’, ‘publicMethod’],
data() {
return {
publicData: ‘foo’,
privateData: ‘bar’
}
},
methods: {
publicMethod() {
/ … /
},
privateMethod() {
/ … /
}
}
}
<a name="D2lE5"></a>
## 组件基础
<a name="H9IHa"></a>
### 定义一个组件
- 单文件组件 SFC , .vue文件
javascript
- 不使用构建时
javascript
export default {
data() {
return {
count: 0
}
},
template: <button @click="count++">
You clicked me {{ count }} times.
</button>
}
- 上面的例子中定义了一个组件,并在一个 .js 文件里默认导出了它自己,但你也可以通过具名导出在一个文件中导出多个组件。
<a name="zI3db"></a>
### 使用组件
- 要使用一个子组件,我们需要在父组件中导入它。
- 若要将导入的组件暴露给模板,我们需要在 components 选项上[注册](https://staging-cn.vuejs.org/guide/components/registration.html)它。这个组件将会以其注册时的名字作为模板中的标签名。
- 当然,你也可以全局地注册一个组件,使得它在当前应用中的任何组件上都可以使用,而不需要额外再导入。
- 组件可以被重用任意多次,每当你使用一个组件,就创建了一个新的**实例**,互不影响。
javascript
这里是一个子组件!
<a name="tA5DC"></a>
### 传递 props
javascript
{{ title }}
<a name="jnrxW"></a>
### 监听事件
- [emit使用](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBCbG9nUG9zdCBmcm9tICcuL0Jsb2dQb3N0LnZ1ZSdcbiAgXG5leHBvcnQgZGVmYXVsdCB7XG4gIGNvbXBvbmVudHM6IHtcbiAgICBCbG9nUG9zdFxuICB9LFxuICBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBwb3N0czogW1xuICAgICAgICB7IGlkOiAxLCB0aXRsZTogJ015IGpvdXJuZXkgd2l0aCBWdWUnIH0sXG4gICAgICAgIHsgaWQ6IDIsIHRpdGxlOiAnQmxvZ2dpbmcgd2l0aCBWdWUnIH0sXG4gICAgICAgIHsgaWQ6IDMsIHRpdGxlOiAnV2h5IFZ1ZSBpcyBzbyBmdW4nIH1cbiAgICAgIF0sXG4gICAgICBwb3N0Rm9udFNpemU6IDFcbiAgICB9XG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxkaXYgOnN0eWxlPVwieyBmb250U2l6ZTogcG9zdEZvbnRTaXplICsgJ2VtJyB9XCI+XG4gICAgPEJsb2dQb3N0XG4gICAgICB2LWZvcj1cInBvc3QgaW4gcG9zdHNcIlxuICAgICAgOmtleT1cInBvc3QuaWRcIlxuICAgICAgOnRpdGxlPVwicG9zdC50aXRsZVwiXG4gICAgICBAZW5sYXJnZS10ZXh0PVwicG9zdEZvbnRTaXplICs9IDAuMVwiXG4gICAgPjwvQmxvZ1Bvc3Q+XG4gIDwvZGl2PlxuPC90ZW1wbGF0ZT4iLCJpbXBvcnQtbWFwLmpzb24iOiJ7XG4gIFwiaW1wb3J0c1wiOiB7XG4gICAgXCJ2dWVcIjogXCJodHRwczovL3NmYy52dWVqcy5vcmcvdnVlLnJ1bnRpbWUuZXNtLWJyb3dzZXIuanNcIlxuICB9XG59IiwiQmxvZ1Bvc3QudnVlIjoiPHNjcmlwdD5cbmV4cG9ydCBkZWZhdWx0IHtcbiAgcHJvcHM6IFsndGl0bGUnXSxcbiAgZW1pdHM6IFsnZW5sYXJnZS10ZXh0J11cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxkaXYgY2xhc3M9XCJibG9nLXBvc3RcIj5cblx0ICA8aDQ+e3sgdGl0bGUgfX08L2g0PlxuXHQgIDxidXR0b24gQGNsaWNrPVwiJGVtaXQoJ2VubGFyZ2UtdGV4dCcpXCI+RW5sYXJnZSB0ZXh0PC9idXR0b24+XG4gIDwvZGl2PlxuPC90ZW1wbGF0ZT4ifQ==)
<a name="eZSwN"></a>
### 通过插槽来分配内容
- [slot使用](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBBbGVydEJveCBmcm9tICcuL0FsZXJ0Qm94LnZ1ZSdcbiAgXG5leHBvcnQgZGVmYXVsdCB7XG4gIGNvbXBvbmVudHM6IHsgQWxlcnRCb3ggfVxufVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cblx0PEFsZXJ0Qm94PlxuICBcdFNvbWV0aGluZyBiYWQgaGFwcGVuZWQuXG5cdDwvQWxlcnRCb3g+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0iLCJBbGVydEJveC52dWUiOiI8dGVtcGxhdGU+XG4gIDxkaXYgY2xhc3M9XCJhbGVydC1ib3hcIj5cbiAgICA8c3Ryb25nPkVycm9yITwvc3Ryb25nPlxuICAgIDxici8+XG4gICAgPHNsb3QgLz5cbiAgPC9kaXY+XG48L3RlbXBsYXRlPlxuXG48c3R5bGUgc2NvcGVkPlxuLmFsZXJ0LWJveCB7XG4gIGNvbG9yOiAjNjY2O1xuICBib3JkZXI6IDFweCBzb2xpZCByZWQ7XG4gIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgcGFkZGluZzogMjBweDtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2Y4ZjhmODtcbn1cbiAgXG5zdHJvbmcge1xuXHRjb2xvcjogcmVkOyAgICBcbn1cbjwvc3R5bGU+In0=)
<a name="o6UXV"></a>
### 动态组件
- [动态组件](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbmltcG9ydCBIb21lIGZyb20gJy4vSG9tZS52dWUnXG5pbXBvcnQgUG9zdHMgZnJvbSAnLi9Qb3N0cy52dWUnXG5pbXBvcnQgQXJjaGl2ZSBmcm9tICcuL0FyY2hpdmUudnVlJ1xuICBcbmV4cG9ydCBkZWZhdWx0IHtcbiAgY29tcG9uZW50czoge1xuICAgIEhvbWUsXG4gICAgUG9zdHMsXG4gICAgQXJjaGl2ZVxuICB9LFxuICBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBjdXJyZW50VGFiOiAnSG9tZScsXG4gICAgICB0YWJzOiBbJ0hvbWUnLCAnUG9zdHMnLCAnQXJjaGl2ZSddXG4gICAgfVxuICB9XG59XG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8ZGl2IGNsYXNzPVwiZGVtb1wiPlxuICAgIDxidXR0b25cbiAgICAgICB2LWZvcj1cInRhYiBpbiB0YWJzXCJcbiAgICAgICA6a2V5PVwidGFiXCJcbiAgICAgICA6Y2xhc3M9XCJbJ3RhYi1idXR0b24nLCB7IGFjdGl2ZTogY3VycmVudFRhYiA9PT0gdGFiIH1dXCJcbiAgICAgICBAY2xpY2s9XCJjdXJyZW50VGFiID0gdGFiXCJcbiAgICAgPlxuICAgICAge3sgdGFiIH19XG4gICAgPC9idXR0b24+XG5cdCAgPGNvbXBvbmVudCA6aXM9XCJjdXJyZW50VGFiXCIgY2xhc3M9XCJ0YWJcIj48L2NvbXBvbmVudD5cbiAgPC9kaXY+XG48L3RlbXBsYXRlPlxuXG48c3R5bGU+XG4uZGVtbyB7XG4gIGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmO1xuICBib3JkZXI6IDFweCBzb2xpZCAjZWVlO1xuICBib3JkZXItcmFkaXVzOiAycHg7XG4gIHBhZGRpbmc6IDIwcHggMzBweDtcbiAgbWFyZ2luLXRvcDogMWVtO1xuICBtYXJnaW4tYm90dG9tOiA0MHB4O1xuICB1c2VyLXNlbGVjdDogbm9uZTtcbiAgb3ZlcmZsb3cteDogYXV0bztcbn1cblxuLnRhYi1idXR0b24ge1xuICBwYWRkaW5nOiA2cHggMTBweDtcbiAgYm9yZGVyLXRvcC1sZWZ0LXJhZGl1czogM3B4O1xuICBib3JkZXItdG9wLXJpZ2h0LXJhZGl1czogM3B4O1xuICBib3JkZXI6IDFweCBzb2xpZCAjY2NjO1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIGJhY2tncm91bmQ6ICNmMGYwZjA7XG4gIG1hcmdpbi1ib3R0b206IC0xcHg7XG4gIG1hcmdpbi1yaWdodDogLTFweDtcbn1cbi50YWItYnV0dG9uOmhvdmVyIHtcbiAgYmFja2dyb3VuZDogI2UwZTBlMDtcbn1cbi50YWItYnV0dG9uLmFjdGl2ZSB7XG4gIGJhY2tncm91bmQ6ICNlMGUwZTA7XG59XG4udGFiIHtcbiAgYm9yZGVyOiAxcHggc29saWQgI2NjYztcbiAgcGFkZGluZzogMTBweDtcbn1cbjwvc3R5bGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIkhvbWUudnVlIjoiPHRlbXBsYXRlPlxuICA8ZGl2IGNsYXNzPVwidGFiXCI+XG4gICAgSG9tZSBjb21wb25lbnRcbiAgPC9kaXY+XG48L3RlbXBsYXRlPiIsIlBvc3RzLnZ1ZSI6Ijx0ZW1wbGF0ZT5cbiAgPGRpdiBjbGFzcz1cInRhYlwiPlxuICAgIFBvc3RzIGNvbXBvbmVudFxuICA8L2Rpdj5cbjwvdGVtcGxhdGU+IiwiQXJjaGl2ZS52dWUiOiI8dGVtcGxhdGU+XG4gIDxkaXYgY2xhc3M9XCJ0YWJcIj5cbiAgICBBcmNoaXZlIGNvbXBvbmVudFxuICA8L2Rpdj5cbjwvdGVtcGxhdGU+In0=)
```javascript
<component :is="currentTab"></component>
DOM 模板解析注意事项
大小写区分
``javascript
// JavaScript 中的 camelCase
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost']
template:
{{ postTitle }}
`
}<a name="uG084"></a>
#### 闭合标签
- 在 DOM 模板中,我们必须显式地写出关闭标签
<a name="R3nvq"></a>
#### 元素位置限制
- 某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul>,<ol>,<table> 和 <select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li>,<tr> 和 <option>。
- s
```html
<table>
<tr is="vue:blog-post-row"></tr>
</table>
深入组件
组件注册
全局注册
import { createApp } from 'vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',
// 组件的实现
{
/* ... */
}
)
//*******
import MyComponent from './App.vue`
app.component('MyComponent', MyComponent)
局部注册
<a name="SuBCi"></a>
### 组件名格式
- 在整个指引中,我们都使用 PascalCase 作为组件名字的注册格式,这是因为:
1. PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 作导入和注册组件都很容易,同时 IDE 也能提供较好的的自动补全。
2. <PascalCase /> 使得 Vue 组件在模板中相较于一个一般的原生 HTML 元素更明显。同时也将 Vue 组件和自定义组件区分开来 (Web components)。
- Vue 支持将那些用 PascalCase 注册的组件转换为 kebab-case 形式的标签名。这意味着一个以 MyComponent 为名注册的组件,可以在模板中既可以使用 <MyComponent> 也可以使用 <my-component>
<a name="ZElb2"></a>
## Props
<a name="h1lie"></a>
### 声明
javascript
export default {
props: [‘foo’],
created() {
// props 会暴露到 this
上
console.log(this.foo)
}
}
export default {
props: {
title: String,
likes: Number
}
}
<a name="x6C9n"></a>
### 传递 props 的细节
- 如果 prop 的名字很长,应使用 camelCase 形式
<a name="KyB64"></a>
#### 静态 vs. 动态 Props
javascript
<a name="nUKFc"></a>
#### 传递不同的值类型
javascript
<a name="bav9A"></a>
#### 使用一个对象绑定多个 prop
javascript
export default {
data() {
return {
post: {
id: 1,
title: ‘我的 Vue 学习之旅’
}
}
}
}
<a name="uQC00"></a>
### 单向数据流
- 所有的 prop 都遵循着单向绑定原则,prop 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改了父组件的状态,不然应用的数据流就会变得难以理解了。
<a name="UFxTk"></a>
#### 更改对象 / 数组类型的 prop
- 当对象或数组作为 prop 被传入时,虽然子组件无法更改 prop 绑定,但仍然**可以**更改对象或数组内部的值
<a name="Km8ew"></a>
## Prop 校验
javascript
export default {
props: {
// 基础类型检查(给出 null
和 undefined
值则会跳过任何类型校验)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props
// 作为参数
default(rawProps) {
// default 函数接收传入的原始 props 作为参数
return { message: ‘hello’ }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return [‘success’, ‘warning’, ‘danger’].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return ‘Default function’
}
}
}
}
<a name="vU3h9"></a>
#### 运行时类型检查
<a name="a5d5m"></a>
### Boolean 类型转换
javascript
export default {
props: {
disabled: Boolean
}
}
```html
<!-- equivalent of passing :disabled="true" -->
<MyComponent disabled />
<!-- equivalent of passing :disabled="false" -->
<MyComponent />
## 组件事件
### 抛出与监听事件
- 注意我们抛出的是一个名字是 camelCase 形式的事件,但可以在父组件中使用 kebab-case 形式来监听。
html
<button @click="$emit('someEvent')">点击这里抛出事件</button>
<MyComponent @some-event="callback" />
<MyComponent @some-event.once="callback" />
### 事件参数
html
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
<MyButton @increase-by="(n) => count += n" />
<MyButton @increase-by="increaseCount" />
methods: {
increaseCount(n) {
this.count += n
}
}
### 声明抛出的事件
javascript
export default {
emits: ['inFocus', 'submit']
}
- 如果一个原生事件的名字 (例如 click) 被定义在 emits 选项中,则监听器只会监听组件发出的 click 事件而不会再响应原生的 click 事件。
### 事件校验
javascript
export default {
emits: {
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
}
#### 配合 v-model
- v-model
#### 处理 v-model 修饰符
- 在 Playground 中尝试一下
### 透传Attribute
### Atrribute继承
- 如果一个子组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并。
- 同样的规则也适用于 v-on 事件监听器:
### 禁用 Attribute 继承
我们想要所有像 class 和 v-on 监听器这样的透传 attribute 都应用在内部的 javascript
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">点击此处</button>
</div>
### 多根节点的 Attribute 继承
javascript
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
### javascripts中访问透传 Attributes
javascript
export default {
created() {
console.log(this.$attrs)
}
}
## 插槽
### 插槽内容与插口
- 插槽内容不仅仅局限于文本。它也可以是任意合法的模板内容,例如我们可以传入一些元素,甚至是组件:
- 插槽内容可以访问到父组件的数据,因为插槽内容本身也是在父组件模板的一部分
### 默认内容
javascript
<button type="submit">
<slot>
提交 <!-- 默认内容 -->
</slot>
</button>
### 具名插槽
### 动态插槽名
```javascript
<a name="Yw1Nx"></a>
### 作用域插槽
- 然而在某些场景下插槽的内容可能想要同时利用父组件域内和子组件域内的数据。要做到这一点,我们需要让子组件将一部分数据在渲染时提供给插槽。
```html
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComonent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
<MyComponent>
无渲染组件
export default {
provide: {
message: 'hello!'
}
}
//下面做法 请注意这不会使注入保持响应性
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
// 使用函数的形式,可以访问到 `this`
return {
message: this.message
}
}
}
//-----------
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
inject
export default {
inject: ['message'],
created() {
console.log(this.message) // injected value
}
}
export default {
inject: ['message'],
data() {
return {
// 基于注入值的初始数据
fullMessage: this.message
}
}
}
注入的别名
export default {
inject: {
/* 局部注入名 */ localMessage: {
from: /* 原注入名 */ 'message'
}
}
}
注入的默认值
export default {
// 当声明注入的默认值时
// 必须使用对象形式
inject: {
message: {
from: 'message', // 当与原注入名同名时,这个属性是可选的
default: 'default value'
},
user: {
// 对于非基础类型数据,如果创建开销比较大,或是需要确保每个组件实例
// 需要独立数据的,请使用工厂函数
default: () => ({ name: 'John' })
}
}
}
配合响应性
<a name="ebdCS"></a>
### 使用 Symbol 作注入名
- 但如果你正在构建大型的应用程序,包含非常多的依赖供给,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。
```javascript
// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { myInjectionKey } from './keys.js'
export default {
provide() {
return {
[myInjectionKey]: {
/* 要供给的数据 */
}
}
}
}
// 注入方组件
import { myInjectionKey } from './keys.js'
export default {
inject: {
injected: { from: myInjectionKey }
}
}
异步组件
基础使用
<a name="s6sAe"></a>
### 加载与错误状态
- 异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent() 也支持在选项中处理这些状态:
```javascript
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载完成的组件前的延迟,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超过了该时间
// 也会显示报错组件
timeout: 3000
})
搭配 Suspense 使用
可重用性
可组合函数
鼠标跟踪器示例
异步状态示例
升级版的useFetch()
```javascript
// fetch.js
import { ref, isRef, unref, watchEffect } from ‘vue’<a name="EilIi"></a>
### 约定和最佳实践
<a name="uEIeG"></a>
#### 命名
- 约定将可组合函数命名为以“use”开头的函数。
<a name="g1gJb"></a>
#### 输入参数
- 如果你正在编写一个提供给其他开发者使用的可组合函数,最好处理输入参数是 ref 而不是原始值。[unref()](https://staging-cn.vuejs.org/api/reactivity-utilities.html#unref) 工具函数在此时带来极大方便:
```javascript
import { unref } from 'vue'
function useFeature(maybeRef) {
// 若 maybeRef 的确是一个 ref,它的 .value 会被返回
// 否则返回其本身
const value = unref(maybeRef)
}
返回值
副作用
使用限制