Vue 3.0

# Vue 3.0

Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

# 创建一个 Vue 应用

每个 Vue 应用都是通过 createApp (opens new window) 函数创建一个新的 应用实例.

import { createApp } from 'vue'

const app = createApp({
  /* 根组件选项 */
})
1
2
3
4
5

我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。

如果你使用的是单文件组件,我们可以直接从另一个文件中导入根组件。

# 导入组件

我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。

如果你使用的是单文件组件,我们可以直接从另一个文件中导入根组件。

import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'

const app = createApp(App)
1
2
3
4
5

# 挂载应用

应用实例必须在调用了 .mount() 方法后才会渲染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串:

html

<div id="app"></div>
1

js

app.mount('#app')
1

应用根组件的内容将会被渲染在容器元素里面。容器元素自己将不会被视为应用的一部分。

.mount() 方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。

# 模板语法

# 文本插值

最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):

<span>Message: {{ msg }}</span>
1

双大括号标签会被替换为相应组件实例中 (opens new window) msg 属性的值。同时每次 msg 属性更改时它也会同步更新。

# 原始 HTML

双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令 (opens new window)

<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
1
2

这里看到的 v-html attribute 被称为一个指令。指令由 v- 作为前缀,表明它们是一些由 Vue 提供的特殊 attribute,你可能已经猜到了,它们将为渲染的 DOM 应用特殊的响应式行为。

# Attribute 绑定

双大括号不能在 HTML attributes 中使用。 想要响应式地绑定一个 attribute,应该使用 v-bind 指令 (opens new window)

<div v-bind:id="dynamicId"></div>
1

v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致。如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。

# 简写

因为 v-bind 非常常用,我们提供了特定的简写语法:

<div :id="dynamicId"></div>
1

开头为 : 的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。

# 动态绑定多个值

如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}
1
2
3
4

通过不带参数的 v-bind,你可以将它们绑定到单个元素上:

<div v-bind="objectOfAttrs"></div>
1

至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>
1
2
3
4
5
6
7

# 调用函数

可以在绑定的表达式中使用一个组件暴露的方法:

<time :title="toTitleDate(date)" :datetime="date">
  {{ formatDate(date) }}
</time>
1
2
3

[!info] TIPS 绑定在表达式中的方法在组件每次更新时都会被重新调用,因此应该产生任何副作用,比如改变数据或触发异步操作。

# 动态参数

同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:

<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
1
2
3
4
5
6
7
8

这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href

动态参数同样可以绑定在 v-on 参数上

<a @[var]="func"> ... </a>
1

其中 var 是一个变量,表示监听的行为,可以是 click, hover 等等。

# 响应式基础

# ref()

在组合式 API 中,推荐使用 ref() (opens new window) 函数来声明响应式状态:

import { ref } from 'vue'

const count = ref(0)
1
2
3

ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
1
2
3
4
5
6
7

要在组件模板中访问 ref,请从组件的 setup() 函数中声明并返回它们:

import { ref } from 'vue'

export default {
  // `setup` 是一个特殊的钩子,专门用于组合式 API。
  setup() {
    const count = ref(0)

    // 将 ref 暴露给模板
    return {
      count
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<div>{{ count }}</div>
1

[!INFO] 注意

  • return 里的内容是暴露给HTML的,只有暴露的变量或函数才能被调用。
  • 在模板中使用 ref 时,我们不需要附加 .value。为了方便起见,当在模板中使用时,ref 会自动解包。

# <script setup>

在 setup() 函数中手动暴露大量的状态和方法非常繁琐。 幸运的是,我们可以通过使用单文件组件 (SFC) (opens new window) 来避免这种情况。我们可以使用 <script setup> 来大幅度地简化代码:

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# reactive()

还有另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:

import { reactive } from 'vue'

const state = reactive({ count: 0 })
1
2
3

响应式对象是 JavaScript 代理 (opens new window),其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。

reactive() 将深层地转换对象:当访问嵌套对象时,它们也会被 reactive() 包装。当 ref 的值是一个对象时,ref() 也会在内部调用它。与浅层 ref 类似,这里也有一个 shallowReactive() (opens new window) API 可以选择退出深层响应性。

即:reactive 是用在对象中的,产生一个响应式的对象。

# 注意事项

  • 与 reactive 对象不同的是,当 ref 作为响应式数组或原生集合类型(如 Map) 中的元素被访问时,它不会被解包:
    const books = reactive([ref('Vue 3 Guide')]) 
    // 这里需要 .value console.log(books[0].value) 
    const map = reactive(new Map([['count', ref(0)]])) 
    // 这里需要 .value console.log(map.get('count').value)
    
    1
    2
    3
    4

# 计算属性

如果一个响应式状态是通过一些方法计算出来的,可以使用 计算属性 来避免在HTML中过于复杂的表示。

如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍。

const publishedBooksMessage = computed(() => {
	return author.books.length > 0 ? 'Yes' : 'No' 
})
1
2
3

计算属性computed(() => {}) 来定义。其中返回的是复杂处理后的结果。

# 计算属性与函数的区别

也许,一个计算属性也可以这么写,执行结果与计算属性无区别:

function calculateBooksMessage() {
  return author.books.length > 0 ? 'Yes' : 'No'
}
1
2
3

但实际上是有区别的。一个计算属性仅会在其响应式依赖更新时才重新计算。 这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。

而函数则不一样,调用一次函数就会重新计算一遍结果,如果计算过程复杂,会占用大量系统资源。

# 注意事项

# Getter 不应有副作用​

计算属性的 getter 应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。 举例来说,不要改变其他状态、在 getter 中做异步请求或者更改 DOM!一个计算属性的声明中描述的是如何根据其他值派生一个值。因此 getter 的职责应该仅为计算和返回该值。 在之后的指引中我们会讨论如何使用侦听器根据其他响应式状态的变更来创建副作用。

# 避免直接修改计算属性值​

从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。

# Class 与 Style 绑定

数据绑定的一个常见需求场景是操纵元素的CSS class 列表和内联样式。因为 classstyle 都是 attribute,我们可以和其他 attribute 一样使用 v-bind 将它们和动态的字符串绑定。

但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 classstylev-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。

# Class 绑定

我们可以给 :class (v-bind:class 的缩写) 传递一个对象来动态切换 class:

<div :class="{ active: isActive }"></div>
1

上面代码 active 是否存在,取决于 isActive 的值。

绑定的对象并不一定需要写成内联字面量的形式,也可以直接绑定一个对象:

const classObject = reactive({
  active: true,
  'text-danger': false
})
1
2
3
4
<div :class="classObject"></div>
1

我们也可以绑定一个返回对象的 计算属性 。这是一个常见且很有用的技巧。

如果你也想在数组中有条件地渲染某个 class,你可以使用三元表达式:

<div :class="[isActive ? activeClass : '', errorClass]"></div>
1

errorClass 会一直存在,但 activeClass 只会在 isActive 为真时才存在。

# 绑定内联样式

直接绑定一个样式对象通常是一个好主意,这样可以使模板更加简洁:

const styleObject = reactive({
  color: 'red',
  fontSize: '13px'
})
1
2
3
4
<div :style="styleObject"></div>
1

# 条件渲染

# v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。

<h1 v-if="awesome">Vue is awesome!</h1>
1

# <template> 上的 v-if

因为 v-if 是一个指令,他必须依附于某个元素。但如果我们想要切换不止一个元素呢?在这种情况下我们可以在一个 <template> 元素上使用 v-if,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>
1
2
3
4
5

v-else 和 v-else-if 也可以在 <template> 上使用。

# v-show

另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样:

<h1 v-show="ok">Hello!</h1>
1

不同之处在于 v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性。

v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。

# v-showv-if 的区别

总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

# 列表渲染