vue3 学习笔记
vue-cli 从 4.5.0 版本开始支持 vue3.0
vue-router 从 4.0 版本开始支持 vue3.0
vuex 从 4.0 版本开始支持 vue3.0。其API与3.x基本相同。唯一的突破性变化是插件的安装方式
新增特性
- Composition(组合)API
- setup
- ref 和 reactive
- computed 和 watch
- 新的生命周期函数
- provide 与 inject
- …
- 新组件
- Fragment - 文档碎片
- Teleport - 瞬移组件的位置
- Suspense - 异步加载组件的 loading 界面
- 其它 API 更新
- 全局 API 的修改
- 将原来的全局 API 转移到应用列表
- 模板语法变化
小改动
destroyed
生命周期选项被重命名为unmounted
beforeDestroy
生命周期选项被重命名为beforeUnmount
- prop
default
工厂函数不再有权访问this
是上下文 - 自定义指令API已更改为与组件生命周期一致
data
应始终声明为函数- 来自 mixin 的
data
选项现在可简单地合并 - attribute 强制策略已更改
- 一些过度 class 被重命名
- 组建 watch 选项和实例方法
$watch
不再支持以点分割的字符串路径。请改用计算属性函数作为参数 <template>
没有特殊指令的标记(v-if / else、v-for 或 v-slot
)现在被视为普通元素,并将生成原生的<template>
元素,而不是渲染其内部内容- 应用根容器本身不再被视为模板的一部分
组合式API
组合式API基础
setup
组件选项
setup
在创建组件实例之前执行。所以setup
中没有this
,除props
外,无法访问组件中声明的任何属性 —— 本地状态、计算属性或方法
setup
选项是一个接受 props
和 context
的函数,该函数返回的所有内容都将暴露给组件的其余部分(计算属性、方法、生命周期钩子等)以及组件的模板
Props
setup
函数中的 props
是响应式的。不能使用ES6解构,否则它会消除 props 的响应性。
如果需要解构 prop,可以通过使用 setup
函数中的 toRefs
1 | import { toRefs } from 'vue' |
上下文
传递给 setup
函数的第二个参数是 context
。context
是一个普通的 JavaScript 对象,它暴露三个组件的属性
1 | export default { |
context
是一个普通的 JavaScript 对象,它不是响应式的,所以可以使用ES6解构
1 | export default { |
带 ref
的响应式变量
可以通过一个新的 ref
函数使任何响应式变量在任何地方起作用
ref
接受参数并返回它包装在具有 value 属性的对象中,然后可以使用该属性访问或更改响应式变量的值
1 | import { ref } from 'vue' |
ref
和 toRef
的区别
1、 ref 本质是拷贝,修改响应式数据不会影响原始数据;toRef 的本质是引用关系,修改响应式数据会影响原始数据
2、 ref 的数据发生改变,界面会自动更新;toRef 当数据发生改变时,界面不会自动更新
3、 toRef 传参与 ref 不同。toRef 接受两个参数,第一个参数是对象,第二个参数是对象的某个属性。如果要遍历对象上的所有属性,可以使用 toRefs
生命周期钩子注册内部 setup
在 3.x,每个生命周期函数都要先导入才可以使用
组合式 API 上的生命周期钩子与选项式 API 的名称相同,但前缀为 on
,即 mounted
看起来像 onmounted
2.x生命周期 | 3.x生命周期 | 说明 |
---|---|---|
beforeCreate | setup | 组件创建前执行 |
created | setup | 组件创建后执行 |
beforeMount | onBeforeMount | 组件挂载到节点之前执行 |
mounted | onMounted | 组件挂载完成后执行 |
beforeUpdate | onBeforeUpdate | 组件更新之前执行 |
updated | onUpdated | 组件更新完成后执行 |
beforeDestroy | onBeforeUnmount | 组件销毁之前执行 |
destroyed | onUnmounted | 组件卸载之后执行 |
errorCaptured | onErrorCaptured | 当捕获一个来自子孙组件的异常时激活的钩子函数 |
被包含在 <keep-alive>
中的组件,会多出两个生命周期钩子函数
2.x生命周期 | 3.x生命周期 | 说明 |
---|---|---|
activated | onActivated | 被激活时执行 |
deactivated | onDeactivated | 切换组件后,原组件消失前执行 |
页面第一次进入的时候,钩子函数触发的顺序是 created -> mounted -> activated
页面退出的时候会触发 deactivated,当再次前进或者后退的时候只触发 activated
响应式API
响应式基础API
reactive
返回 对象
的响应式副本。响应式转换是“深层”的。建议只使用响应式代理对象,避免依赖原始对象
- 返回的代理不等于原始对象
1 | import { reactive } from 'vue' |
- 修改响应式对象,原始对象也会跟着修改,同时视图更新;修改原始对象,响应式对象也会跟着修改,但是视图不会更新
readonly
isProxy
isReactive
isReadonly
toRaw
markRaw
shallowReactive
shallowReadonly
Refs
ref
接受一个 基本数据类型
的值并返回一个响应式且可变的 ref 对象,响应式对象的值通过 .value
获取。DOM可以直接写变量获取,不需要写 .value
1 | <span>{{count}}</span> |
unref
toRef
用来为响应式对象
上的属性创建 ref
,然后可以将 ref 传递出去,从而保持对源属性的响应式连接。
为普通对象的属性使用 toRef,也会为该属性创建 ref ,但是修改值得话视图不会更新,普通对象和 ref 的值会同步更改
1 | const state = reactive({count: 1}) |
toRefs
解决 toRef 为响应式对象
上的多个属性创建 ref 时代码高耦合的问题
1 | const state = reactive({ |
解决响应式对象解构时,丢失响应性的问题
1 | function userData(){ |
isRef
customRef
shallowRef
triggerRef
Computed 与 watch
computed
接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 ref 对象
watchEffect
什么是纯函数?
1、始终返回相同的值。不管调用该函数多少次,相同的输入都要有相同的输出
2、不应修改程序的状态或引起副作用
什么是副作用?
如果有一个函数在输入和输出之外还做了其他的事情,那么这个函数额外做的事情就被成为副作用
VUE 中的副作用?
响应式数据变更造成的其他连锁反应,以及后续逻辑,这些连锁反应都叫副作用
在监听依赖时立即运行一个函数,并且在依赖更改时重新运行它
1 | const count = ref(0) |
停止侦听
1、组件卸载时自动停止
2、将 watchEffevt 赋值给一个变量,执行这个变量即可手动停止
1 | const stop = watchEffect(()=>{ |
清除副作用
onInvalidate
是 effect 函数传入的参数,是一个函数。用于清除 effect 产生的副作用,只作用于异步函数,并且只有在如下两种情况下才会被调用:
1、当 effect 函数被重新调用时
2、当监听器被注销时(如组件被卸载了)
watch
侦听单个数据源
侦听数据源可以是返回值的 getter函数,也可以直接是 ref
如果直接侦听一个对象,两个参数获取的都是当前值,获取不到修改前的值
1 | // 侦听一个 getter |
侦听多个源
同侦听单个的方式,数据源为数组类型
1 | const count=ref(0) |
与 watchEffect 共享行为
<script setup>
<script setup>
是在单文件组件 (SFC) 中使用 组合式API 的编译时语法糖
基本语法
顶层的绑定会被暴露给模板
任何在 <script setup>
声明的顶层的绑定(包括变量、函数声明和 import 引入的内容)都能在模板中直接使用
1 | <script setup> |
响应式
响应式状态需要明确使用 响应式APIs 来创建。和从 setup()
函数中返回值一样,ref 值在模板中使用的时候会自动解包
1 | <script setup> |
使用组件
<script setup>
范围里的值也能被直接作为自定义组件的标签名使用
1 | <script setup> |
动态组件
正常使用语法
递归组件
一个单文件组件可以通过它的文件名被其自己所引用。这种方法相比于 import 导入的组件优先级更低。如果有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入
1 | import { FooBar as FooBarChild } from './components' |
命名空间组件
可以使用带点的组件标记,例如 <Foo.Bar>
来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用
1 | <script setup> |
使用自定义指令
全局注册的自定义指令将以符合预期的方式工作
本地注册的指令必须以 vNameOfDirective
的形式来命名
1 | <script setup> |
defineProps
和 defineEmits
在 <script setup>
中必须使用 defineProps
和 defineEmits
API来声明 props 和 emits,它们具备完整的类型推断并且在 <script setup>
中是可以直接使用的
1 | <script setup> |
defineProps
和defineEmits
都是只在<script setup>
中才能使用的编译器宏。它们不需要导入且会随着<script setup>
处理过程一同被编译掉
defineProps
接收与 props 选项相同的值,defineEmits
接收 emits 选项相同的值defineProps
和defineEmits
在选项传入后,会提供恰当的类型推断- 传入到
defineProps
和defineEmits
的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量,这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内
defineExpose
使用 <script setup>
的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup>
中声明的绑定
可以使用 defineExpose
编译器宏去暴露 <script setup>
中的属性
1 | <script setup> |
当父组件通过模板 ref 的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number }
(ref会和在普通实例中一样自动解包)
useSlots
和 useAttrs
1 | <script setup> |
useSlots
和 useAttrs
是真实地运行时函数,它会返回与 setupContext.slots
和 setupContext.attrs
等价的值,同样也能在普通的组合式 API 中使用、
与普通的 <script>
一起使用
<script setup>
可以和普通的 <script>
一起使用。普通的 <script>
在有这些需要的情况下或许会被使用到:
- 无法在
<script setup>
声明的选项,例如inheritAttrs
或通过插件启用的自定义的选项 - 声明命名导出
- 运行副作用或者创建只需要执行一次的对象
1 | <script> |
顶层 await
<script setup>
中可以使用顶层 await
。结果代码会被编译成 async setup()
1 | <script setup> |
await 的表达式会自动编译成在 await
之后保留当前组件实例上下文的格式
async setup()
必须与Suspense
组合使用
限制:没有 src 导入
<script setup>
不能和 src
属性一起使用
项目实战笔记
项目初始化
入口文件
回顾2.x
2.0 在导入各种依赖之后,通过 new Vue
来执行初始化。相关的插件,有的使用 Vue.use
初始化,有的是作为 new Vue
的入参,如下图所示:
3.x 方式
3.x 是通过 createApp
来执行初始化,其他统一使用 use
来激活初始化