1.快速上手
安装
1 | npm create vue@latest |
用最朴素的配置
1 | ✔ Project name: … <your-project-name> |
启动
1 | cd <your-project-name> |
2 创建一个应用
每个 Vue 应用都是通过 [createApp](https://cn.vuejs.org/api/application.html#createapp)
函数创建一个新的 应用实例:
1 | import { createApp } from 'vue' |
根组件
我们传入 createApp
的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。
如果你使用的是单文件组件,我们可以直接从另一个文件中导入根组件。
1 | import { createApp } from 'vue' |
挂在应用
应用实例必须在调用了 .mount()
方法后才会渲染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串:
1 | <div id="app"></div> |
1 | app.mount('#app') |
应用根组件的内容将会被渲染在容器元素里面。容器元素自己将不会被视为应用的一部分。
.mount()
方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。
DOM 中的根组件模板
根组件的模板通常是组件本身的一部分,但也可以直接通过在挂载容器内编写模板来单独提供:
1 | <div id="app"> |
1 | import { createApp } from 'vue' |
当根组件没有设置 template
选项时,Vue 将自动使用容器的 innerHTML
作为模板。
DOM 内模板通常用于无构建步骤的 Vue 应用程序。它们也可以与服务器端框架一起使用,其中根模板可能是由服务器动态生成的。
回到我们创建的实例
找到src/main.js文件
源代码是这样的
1 | createApp(App).mount('#app') |
我们可以按照上面的形式改成这样
1 | //根组件 |
3 模板语法
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。
在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。
如果你对虚拟 DOM 的概念比较熟悉,并且偏好直接使用 JavaScript,你也可以结合可选的 JSX 支持直接手写渲染函数而不采用模板。但请注意,这将不会享受到和模板同等级别的编译时优化。
a.Attribute 绑定
双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind
指令:
1 | <div v-bind:id="dynamicId"></div> |
v-bind
指令指示 Vue 将元素的 id
attribute 与组件的 dynamicId
属性保持一致。如果绑定的值是 null
或者 undefined
,那么该 attribute 将会从渲染的元素上移除。
因为 v-bind
非常常用,我们提供了特定的简写语法
1 | <div :id="dynamicId"></div> |
动态绑定多个值
如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:
1 | const objectOfAttrs = { |
通过不带参数的 v-bind
,你可以将它们绑定到单个元素上:
1 | <div v-bind="objectOfAttrs"></div> |
b 使用 JavaScript 表达式
至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:
1 | {{ number + 1 }} |
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
- 在文本插值中 (双大括号)
- 在任何 Vue 指令 (以
v-
开头的特殊 attribute) attribute 的值中
c.指令 Directives
指令是带有 v-
前缀的特殊 attribute。Vue 提供了许多**内置指令**,包括上面我们所介绍的 v-bind
和 v-html
。
指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-for
、v-on
和 v-slot
)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 [v-if](https://cn.vuejs.org/api/built-in-directives.html#v-if)
为例:
1 | <p v-if="seen">Now you see me</p> |
这里,v-if
指令会基于表达式 seen
的值的真假来移除/插入该 <p>
元素。
另一个例子是 v-on
指令,它将监听 DOM 事件:
1 | <a v-on:click="doSomething"> ... </a> |
这里的参数是要监听的事件名称:click
。v-on
有一个相应的缩写,即 @
字符。我们之后也会讨论关于事件处理的更多细节。
d.动态参数
同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:
1 | <!-- |
这里的 attributeName
会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName
,其值为 "href"
,那么这个绑定就等价于 v-bind:href
。
相似地,你还可以将一个函数绑定到动态的事件名称上:
1 | <a v-on:[eventName]="doSomething"> ... </a> |
e.修饰符 Modifiers
修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent
修饰符会告知 v-on
指令对触发的事件调用 event.preventDefault()
:
1 | <form @submit.prevent="onSubmit">...</form> |
之后在讲到 [v-on](https://cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers)
和 [v-model](https://cn.vuejs.org/guide/essentials/forms.html#modifiers)
的功能时,你将会看到其他修饰符的例子。
最后,在这里你可以直观地看到完整的指令语法:
4.声明响应式状态
a.ref()
在组合式 API 中,推荐使用 [ref()](https://cn.vuejs.org/api/reactivity-core.html#ref)
函数来声明响应式状态:
1 | import { ref } from 'vue' |
ref()
接收参数,并将其包裹在一个带有 .value
属性的 ref 对象中返回:
1 | const count = ref(0) |
要在组件模板中访问 ref,请从组件的 setup()
函数中声明并返回它们:
1 | import { ref } from 'vue' |
1 | <div>{{ count }}</div> |
注意,在模板中使用 ref 时,我们不需要附加 .value
。为了方便起见,当在模板中使用时,ref 会自动解包 (有一些**注意事项**)。
你也可以直接在事件监听器中改变一个 ref:
1 | <button @click="count++"> |
对于更复杂的逻辑,我们可以在同一作用域内声明更改 ref 的函数,并将它们作为方法与状态一起公开:
1 | import { ref } from 'vue' |
然后,暴露的方法可以被用作事件监听器:
1 | <button @click="increment"> |
b**<script setup>
**
在 setup()
函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用 <script setup>
来大幅度地简化代码:
1 | <script setup> |
<script setup>
中的顶层的导入、声明的变量和函数可在同一组件的模板中直接使用。你可以理解为模板是在同一作用域内声明的一个 JavaScript 函数——它自然可以访问与它一起声明的所有内容。
c**reactive()
**
还有另一种声明响应式状态的方式,即使用 reactive()
API。与将内部值包装在特殊对象中的 ref 不同,reactive()
将使对象本身具有响应性:
1 | import { reactive } from 'vue' |
在模板中使用:
1 | <button @click="state.count++"> |
响应式对象是 JavaScript 代理,其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。
reactive()
将深层地转换对象:当访问嵌套对象时,它们也会被 reactive()
包装。当 ref 的值是一个对象时,ref()
也会在内部调用它。与浅层 ref 类似,这里也有一个 shallowReactive()
API 可以选择退出深层响应性。
值得注意的是,reactive()
返回的是一个原始对象的 **Proxy**,它和原始对象是不相等的:(因为一个是对象一个是{})
1 | const raw = {} |
reactive()
的局限性
- 有限的值类型:它只能用于对象类型 (对象、数组和如
Map
、Set
这样的**集合类型)。它不能持有如string
、number
或boolean
这样的原始类型**。 - 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
1 | let state = reactive({ count: 0 }) |
- 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
1 | const state = reactive({ count: 0 }) |
由于这些限制,我们建议使用 ref()
作为声明响应式状态的主要 API。
5.计算属性
a.基础示例
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象:
1 | const author = reactive({ |
我们想根据 author
是否已有一些书籍来展示不同的信息:
1 | <p>Has published books:</p> |
这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于 author.books
。更重要的是,如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍。
因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。这是重构后的示例:
1 | <script setup> |
我们在这里定义了一个计算属性 publishedBooksMessage
。computed()
方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value
访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value
。
Vue 的计算属性会自动追踪响应式依赖。它会检测到 publishedBooksMessage
依赖于 author.books
,所以当 author.books
改变时,任何依赖于 publishedBooksMessage
的绑定都会同时更新。
b.计算属性缓存 vs 方法
你可能注意到我们在表达式中像这样调用一个函数也会获得和计算属性相同的结果:
1 | <p>{{ calculateBooksMessage() }}</p> |
1 | // 组件中 |
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books
不改变,无论多少次访问 publishedBooksMessage
都会立即返回先前的计算结果,而不用重复执行 getter 函数。
这也解释了为什么下面的计算属性永远不会更新,因为 Date.now()
并不是一个响应式依赖:
1 | const now = computed(() => Date.now()) |
相比之下,方法调用总是会在重渲染发生时再次执行函数。
为什么需要缓存呢?想象一下我们有一个非常耗性能的计算属性 list
,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list
。没有缓存的话,我们会重复执行非常多次 list
的 getter,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。
c.可写计算属性
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:
1 | <script setup> |
现在当你再运行 fullName.value = 'John Doe'
时,setter 会被调用而 firstName
和 lastName
会随之更新。
soga好神奇
今天学到这里,下课!