Vue3学习笔记
Vue3学习笔记
学习时间:2024年7月30日
学习来源:Vue官方文档
1 简介
Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。
1.1 单文件组件
在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件 (也被称为 *.vue
文件,英文 Single-File Components,缩写为 SFC)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。
计数器示例
1 | <script setup> |
1.2 API风格
Vue 的组件可以按两种不同的风格书写:选项式 API(Options API
) 和组合式 API(Composition API
)。
1.2.1 Options API
使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 mounted
。选项所定义的属性都会暴露在函数内部的 this
上,它会指向当前的组件实例。
1 | <script> |
1.2.2 Composition API
在单文件组件中,组合式 API 通常会与 <script setup>
搭配使用。这个 setup
是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup>
中的导入和顶层变量/函数都能够在模板中直接使用。
下面是使用了组合式 API 与 <script setup>
改造后和上面的模板完全一样的组件:
1 | <script setup> |
核心概念
- setup 函数:这是 Composition API 的核心。
setup
函数在组件实例创建之前执行,并且作为组件选项传递。它用于初始化组件的状态、定义响应式数据和计算属性、并声明生命周期钩子。 - 响应式引用(refs):使用
ref
创建响应式数据。响应式引用是包含.value
属性的对象,可以用来追踪和更新数据变化。ef()
函数可以根据给定的值来创建一个响应式的数据对象,返回值是一个对象,且只包含一个.value
属性。 - 响应式对象(reactive):使用
reactive
创建一个响应式对象。与ref
不同,reactive
直接返回一个响应式代理对象,适用于更复杂的嵌套对象。 - 计算属性(computed):使用
computed
创建计算属性,它依赖于响应式数据并会在依赖发生变化时自动更新。 - 生命周期钩子:Vue 3 提供了一组新的生命周期钩子函数,可以在
setup
函数中调用。
与选项式API的对比
图中色块表示逻辑一致的代码块。可以看到组合式 API允许我们编写更有条理的代码。
生命周期函数对比
Vue2 Options-based API | Vue Composition API |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
2 基础
以下内容均基于组合式API。
2.1 模板语法
2.1.1 文本插值
最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):
1 | <span>Message: {{ msg }}</span> |
双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新。
2.1.2 原始HTML
双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,需要使用 v-html
指令:
1 | <p>Using text interpolation: {{ rawHtml }}</p> |
当rawHtml
设置为:<span style="color: red">This should be red.</span>
时的效果:
1 | Using text interpolation: <span style="color: red">This should be red.</span> |
2.1.3 v-bind绑定
双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind
指令:
1 | <div v-bind:id="dynamicId"></div> |
简写:
1 | <div :id="dynamicId"></div> |
2.2 响应式基础
2.2.1 ref
在组合式 API 中,推荐使用 ref()
函数来声明响应式状态:
1 | import { ref } from 'vue' |
ref()
接收参数,并将其包裹在一个带有 .value
属性的 ref 对象中返回:
1 | const count = ref(0) |
ref
会根据初始化时的值推导其类型:
1 | import { ref } from 'vue' |
其他的示例:
1 | let hMS = ref(null); // null |
要在组件模板中访问 ref,请从组件的 setup()
函数中声明并返回它们:
1 | import { ref } from 'vue' |
1 | <button @click="count++"> |
2.2.2 <script setup>
① 基本语法
在 setup()
函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用 <script setup>
来大幅度地简化代码:
1 | <script setup> |
<script setup>
中的顶层的导入、声明的变量和函数可在同一组件的模板中直接使用。这是实际开发时最常用的格式。
<script setup>
中的代码会在每次组件实例被创建的时候执行。
② defineProps
和defineEmits
defineProps
和 defineEmits
是 Vue 3 中 <script setup>
语法的两个核心 Composition API 函数,用于简化组件的 props
和事件声明。使用 TypeScript 时,能提供更好的类型推断。
defineProps
1 | <template> |
使用类型推断:
1 | <template> |
defineEmits
1 | <template> |
名称对应关系:
更复杂的事件示例:
1 | const emit = defineEmits<{ |
- 结合使用
1 | <template> |
TypeScript的类型推断
TypeScript 自动推断出变量、函数返回值、参数等的类型,而无需显式地声明这些类型。这使得 TypeScript 能够在许多情况下提供类型安全,而无需过多的类型注释。
变量初始化推断: 当你给一个变量赋值时,TypeScript 可以根据这个值自动推断出变量的类型。
1 | let num = 42; // TypeScript 推断 num 的类型为 number |
函数返回值推断: TypeScript 可以根据函数体中的代码自动推断函数的返回类型。
1 | function add(a: number, b: number) { |
参数类型推断: 当你传递参数给一个函数时,TypeScript 会根据参数的类型推断函数的类型。
1 | function greet(name: string) { |
泛型推断: 当使用泛型函数或类时,TypeScript 可以根据传递给它的参数推断出泛型的具体类型。
1 | function identity<T>(value: T): T { |
③ defineExpose
在 Vue 3 Composition API 中,defineExpose
是一个函数,用于在组合式组件中暴露内部方法或数据,以便其父组件可以访问这些方法或数据。
示例
考虑一个简单的计数器组件,我们想要在父组件中调用这个计数器组件内部的递增和重置方法。
Counter.vue
1 | <!-- Counter.vue --> |
- 父组件
1 | <!-- ParentComponent.vue --> |
2.2.3 DOM更新时机 — nextTick
当修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。Vue 会在nextTick
更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
函数类型:
1 | function nextTick(callback?: () => void): Promise<void> |
function nextTick
:这是一个函数声明,函数名为nextTick
。callback?: () => void
:这是一个可选的回调函数参数,类型是() => void
,表示这个回调函数不接受任何参数,也不返回任何值(void
)。问号?
表示这个参数是可选的。: Promise<void>
:表示这个函数的返回类型是一个Promise
,这个Promise
不会返回任何值(void
)。
示例:
- 使用回调函数
1 | import { nextTick } from 'vue' |
在这个示例中,nextTick
接受一个回调函数,该回调函数将在 DOM 更新之后执行。
- 使用
Promise
1 | import { nextTick } from 'vue' |
在这个示例中,nextTick
返回一个 Promise
,可以使用 async/await
语法等待这个 Promise
,确保在 DOM 更新之后再执行后续代码。
异步相关补充
async
关键字:- 将一个普通函数转换为异步函数。
- 异步函数返回一个
Promise
。 - 允许在异步函数内部使用
await
关键字等待Promise
完成。
await
关键字:- 只能在
async
函数内部使用 - 它用于等待一个
Promise
对象的解决(resolved)或拒绝(rejected)。当await
关键字等待的Promise
被解决时,它会暂停函数的执行,并返回Promise
的值。如果Promise
被拒绝,它会抛出一个异常。
- 只能在
默认情况下,nextTick
接受一个普通的回调函数,在下一个 DOM 更新循环之后执行该回调函数。这时候的回调函数本身不是异步函数。
1 | import { nextTick } from 'vue'; |
如果希望在 nextTick
的回调函数中执行异步操作,可以将回调函数定义为异步函数。这允许你在回调函数内部使用 await
。
1 | import { nextTick } from 'vue'; |
在这个示例中:
nextTick
的回调函数被定义为async
函数(异步函数)。- 在回调函数内部,可以使用
await
来等待fetchData
方法完成。 - 这样可以确保在 DOM 更新之后再执行异步操作,并处理其结果。
2.2.4 reactive
还有另一种声明响应式状态的方式,即使用 reactive()
API。与将内部值包装在特殊对象中的 ref 不同,reactive()
将使对象本身具有响应性:
1 | import { reactive } from 'vue' |
在模板中使用:
1 | <button @click="state.count++"> |
示例
1 | <template> |
2.9 生命周期
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。
最常用的是 onMounted
、onUpdated
和 onUnmounted
。
生命周期钩子的执行顺序如下:
- 创建(Creation)阶段:(
setup
)beforeCreate
created
- 挂载(Mounting)阶段:
onBeforeMount
:在组件实例被挂载到 DOM 之前调用。onMounted
:在组件实例被挂载到 DOM 后调用。可以操作DOM。
- 更新(Updating)阶段:
onBeforeUpdate
:在组件数据更新之前调用,适合在 DOM 更新之前执行一些逻辑。onUpdated
:在组件数据更新并且 DOM 更新完成后调用。
- 卸载(Unmounting)阶段:
onBeforeUnmount
:在组件实例卸载之前调用。onUnmounted
:在组件实例卸载之后调用。
2.9.1 onMounted
注册一个回调函数,在组件挂载完成后执行。
1 | function onMounted(callback: () => void): void |
示例
1 | <script setup> |
2.9.2 onUpdated
注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
1 | function onUpdated(callback: () => void): void |
示例
1 | <script setup> |
2.9.3 onUnmounted
注册一个回调函数,在组件实例被卸载之后调用。
1 | function onUnmounted(callback: () => void): void |
2.11 模板引用
虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref
:
1 | <input ref="input"> |
它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。这可能很有用,比如说在组件挂载时将焦点设置到一个 input 元素上,或在一个元素上初始化一个第三方库。
2.11.1 访问模板引用
为了通过组合式 API 获得该模板引用,我们需要声明一个匹配模板 ref 属性值的 ref:
1 | <script setup> |
注意,只可以在组件挂载后才能访问模板引用。
2.11.2 组件上的ref
模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例。
示例
- 父组件
1 | <!-- ParentComponent.vue --> |
- 子组件
1 | <!-- ChildComponent.vue --> |
3 组件
3.1 组件基础
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
3.1.1 定义组件
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的.vue
文件中,这被叫做单文件组件 (简称 SFC):
1 | <!-- ButtonCounter.vue --> |
3.1.2 使用组件
要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue
的文件中,这个组件将会以默认导出的形式被暴露给外部。
1 | <script setup> |
3.2 props
props
(properties)是组件用于接收来自父组件的数据的机制,由子组件声明,子组件接收父组件的数据。它们使得父组件能够将数据传递给子组件,并使子组件能够灵活地复用和处理这些数据。props
可以包含不同类型的数据,例如字符串、数字、数组、对象、函数等。
defineProps
详见2.2.2.②
节
3.2.1 基本用法
1 | <!-- ParentComponent.vue --> |
1 | <!-- ChildComponent.vue --> |
3.2.2 要点
通过声明
props
的类型,可以确保父组件传递的数据类型正确。如果父组件没有传递
props
,可以为props
设置默认值。- 可以为
props
声明多种可能的类型。 - 通过设置
required
属性,可以指定某个prop
是必需的。
1 | <script setup> |
3.2.3 单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告:
1 | const props = defineProps(['foo']) |
3.3 事件
略。defineEmits
详见2.2.2.②
节。
这里给出父组件包含一个子组件,并监听子组件发出的事件的示例。
- 子组件
ChildComponent.vue
1 | <template> |
在这个子组件中,自定义了两个事件:
'update'
事件不接受任何参数。'save'
事件接受一个对象作为参数,包含id
和value
属性。
- 父组件
ParentComponent.vue
1 | <template> |
在这个父组件中,我们引入了子组件 ChildComponent
,并为其定义的事件添加了监听处理函数:
@update
事件触发时,调用handleUpdate
方法。@save
事件触发时,调用handleSave
方法,并接收传递的data
参数。
名称对应关系: