这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
Vue3组合式API简单上手使用
响应式数据
在Vue3中创建响应式的数据主要是通过reactive和ref这两个API实现的,现在我们就来依次学习一下这两个以及相关的API。
reactive
reactive API用于创建响应式的对象或者数组,实际上该方法的内部就是基于ES6的Proxy实现的。
如下代码展示了reactive API的用法:
<template>
<h3>信息展示组件</h3>
<div style="margin: 24px 0">
<span>姓名:</span>
<span>{{ data.name }}</span>
<br />
<span>年龄:</span>
<span>{{ data.age }}</span>
</div>
<button @click="data.name = '一碗周'">修改姓名</button>
<br />
<button @click="data.age = '20'">修改年龄</button>
</template>
<script setup>
// 使用 <script setup> 所有的 API 需要单独引入,除 Vue3.2 自动引入的几个API外。
import { reactive } from 'vue'
// 创建响应式对象
const data = reactive({
name: '一碗粥',
age: '18',
})
</script>
运行结果如下:
Vue中还提供了一个isReactive,该API用于检测是否为reactive创建的响应式代理。
添加ts类型限制,不需额外引入
const searchContainer: Array<string> = reactive(["search"]);
reactive(x) 必须要指定参数,所以类型就已经确定了,也不能增加属性
const count = ref(1)
console.log('ref:', count) //RefImpl{...}
//当ref分配给reactive时,ref将自动解包
const obj = reactive({ a: count }) //不需要count.value
console.log(obj.a) // 1
console.log(obj.a === count.value) // true
//obj.b=7 //添加属性会报错 // { a: number; }上不存在属性b
//const str=reactive("aaa") //这是报错的,reactive参数只能是对象
const arr = reactive([1, 2]) //数组,其实结果还是对象
const obj = reactive({ 0: 1, 1: 2 })
console.log('arr', arr) //Proxy {0: 1, 1: 2}
console.log('obj', obj) //Proxy {0: 1, 1: 2}
//reactive定义和ref不同,ref返回的是Ref<T>类型,reactive不存在Reactive<T>
//它返回是UnwrapNestedRefs<T>,和传入目标类型一致,所以不存在定义通用reactive类型
function reactiveFun<T extends object>(target: T) {
const state = reactive(target) as UnwrapNestedRefs<T>
return state
}
type typPeople = {
name: string
age: number
}
const item: typPeople = { name: 'aa', age: 18 }
const obj1 = reactive(item) //obj1 类型为: { name: string; age: number; }
const obj2 = reactiveFun(item) //obj2 类型为: { name: string; age: number; }
Ref API
我们使用reactive只能对Object或者Array类型的数据进行劫持,如果我们想要对普通数据类型的数据进行劫持,可以使用ref API,例如如下代码:
<template>
<h3>信息展示组件</h3>
<div style="margin: 24px 0">
<span>姓名:</span>
<!-- 在模板中,Vue 会自动帮助我们为 ref 进行解包,所以不需要使用 ref.value 的形式 -->
<span>{{ name }}</span>
<br />
<span>年龄:</span>
<span>{{ age }}</span>
</div>
<button @click="handleEditName">修改姓名</button>
<br />
<button @click="handleEditAge">修改年龄</button>
</template>
<script setup>
// 导入 ref
import { ref } from 'vue'
// 创建响应式对象
const name = ref('一碗粥')
const age = ref('18')
const handleEditName = () => {
// 通过 ref 创造的响应式对象,我们需要通过 ref.value 的方式访问
name.value = '一碗周'
}
const handleEditAge = () => {
age.value = '20'
}
</script>
代码运行结果与上面相同。
为ref添加Ts类型限制
- 声明类型 Ref<string[]> type Ref
import { ref, type Ref } from "vue"; // 声明类型 Ref<string[]> 直接从vue中导入type Ref即可和ref一样
const input: Ref<HTMLElement | null> = ref(null);
- 范型显示约束 ref<string[]>
const b = ref<string[]>([]) //可以通过范型显示约束 ref<string[]>
const a = ref('') //根据输入参数推导字符串类型 Ref<string>
const b = ref<string[]>([]) //可以通过范型显示约束 ref<string[]>
const c: Ref<string[]> = ref([]) //声明类型 Ref<string[]> 直接从vue中导入type Ref即可和ref一样
const list = ref([1, 3, 5])
console.log('list前:', list.value)
list.value[1] = 7
console.log('list后:', list.value)
type typPeople = {
name: string
age: number
}
const list2: Ref<typPeople[]> = ref([])
console.log('list2-前:', list2.value) //{} 不是空数组,而是空对象
list2.value.push({ name: '小张', age: 18 })
console.log('list2-后:', list2.value[0]) //{name: '小张', age: 18}
********* ref 内部值指定类型 *********
const foo = ref<string | number>('foo')
foo.value = 123
********* 如果ref类型未知,则建议将 ref 转换为 Ref<T>: *********
function useState<T>(initial: T) {
const state = ref(initial) as Ref<T>
return state
}
const item: typPeople = { name: 'aa', age: 18 }
const x1 = useState(item) // x1 类型为: Ref<typPeople>
const x2 = ref(item) //x2 类型为: Ref<{ name:string; age: number;}>
console.log('ref.value:', x1.value, x1.value.name)
//Proxy{name: 'aa', age: 18} aa
readonly
有时我们希望我们传递给其他组件数据时,往往希望其他组件使用我们传递的内容,但是不允许它们修改,这个时候就可以使用readonly API,该API可以创建一个不可修改的对象。
我们看下面这段代码:
import { ref, readonly } from 'vue'
// 创建响应式对象
const name = ref('一碗粥')
const age = ref('18')
const readonlyName = readonly(name)
const handleEditName = () => {
// 通过 ref 创造的响应式对象,我们需要通过 ref.value 的方式访问
name.value = '一碗周'
}
const handleEditAge = () => {
age.value = '20'
}
我们修改name时,readonlyName的值也会跟着进行改变,但是直接修改readonlyName并不会生效。
toRefs和toRef
toRefs和toRef用于将reactive创建的响应式代码解构成响应式的数据;如果直接使用ES6的语法进行解构,解构出的数据并不是响应式的。
import { toRefs, reactive } from 'vue'
const user = reactive({ name: '一碗粥', age: '18' })
// user 下的属性通过 toRefs 与解构后的数据建立了链接,任何一个修改都会引起另一个的变化
const { name, age } = toRefs(user)
const handleEditName = () => {
name.value = '一碗周'
}
const handleEditAge = () => {
age.value = '20'
}
例如下面这段代码:
如果想解构单个数据可以使用toRef,示例代码如下:
const name = toRef(user, 'name')
const age = toRef(user, 'age')
计算属性
在Composition API中定义计算属性是通过computed方法实现,它可以接受两种参数,一个是getter函数,另一个是包含get和set函数的对象;computed返回一个ref对象。
如下代码展示接收getter函数时,计算属性的用法:
import { ref, computed } from 'vue'
const name = ref('一碗周')
const age = ref('18')
// 定义计算属性
const user = computed(() => {
return `姓名:${name.value}\n年龄:${age.value}`
})
上面的代码中当name或者age发生变化时,user也会进行变化。
computed方法接受对象参数时,最常见的场景就是组件实现v-model的功能,示例代码如下所示:
<template>
<input type="text" v-model="myName" />
</template>
<script setup>
// 引入需要的方法
import { defineProps, defineEmits, computed } from 'vue'
// 定义 props
const props = defineProps({
name: String,
})
// v-model 规定写法 update:name name->需要v-model的名称
const emit = defineEmits(['update:name'])
// 定义计算属性
const myName = computed({
get() {
return props.name
},
set(val) {
emit('update:name', val)
},
})
</script>
上面的代码展示了如何在computed方法传递对象参数。
对计算属性做类型限制时和ref一样如: const doubleCount: Ref<number> = computed(() => count.value * 2);
监听器
Vue3的composition API中提供了两个用于监听数据变化的API,分别是watchEffect(Vue3.2中新增了watchPostEffect和watchSyncEffect API,这两个都是watchEffect的别名,附带指定选项而已)和watch,这两者的区别如下:
- watchEffect用于自动收集响应式数据的依赖;
- watch需要手动指定监听的数据源;
生命周期
Vue3的composition API没有生命周期钩子选项,但是提供了onBeforeMount、onMounted等函数来注册声明周期钩子,提供的声明周期函数如下表所示:
| 选项式 API | Hook inside setup | 触发时机 |
|---|---|---|
| beforeMount | onBeforeMount | 组件挂载之前触发 |
| mounted | onMounted | 组件挂载后触发 |
| beforeUpdate | onBeforeUpdate | 组件更新之前触发 |
| updated | onUpdated | 组件更新后触发 |
| beforeUnmount | onBeforeUnmount | 组件卸载之前触发 |
| unmounted | onUnmounted | 组件卸载后触发 |
如下代码展示了部分API的用法:
import { onMounted, onUpdated, onUnmounted } from 'vue'
onMounted(() => {
console.log('onMounted')
})
onUpdated(() => {
console.log('onUpdated')
})
onUnmounted(() => {
console.log('onUnmounted')
})
模板ref($refs的代替品)
由于Vue3的composition API无法使用this,所以说this.$refs并不可以用,那我们怎么获取到组件或者元素呢?其实非常简单,我们需要定义个ref对象,名称与模板中ref属性的名称一致即可。
示例代码如下:
<template>
<h3 ref="nameRef">一碗周</h3>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const nameRef = ref(null)
onMounted(() => {
console.log(nameRef.value) // <h3>一碗周</h3>
})
</script>
获取DOM元素时添加TS类型限制
- 增加null类型
const input: Ref<HTMLElement | null> = ref(null)
- 在声明文件(*.d.ts)中定义一个类型声明
declare type Nullable<T> = T | null // 定义声明
// 使用的地方只需要const input: Ref<Nullable<HTMLElement>> = ref(null)
defineProps({xx{type:xx,require:false}) || defineProps([""])
defineEmits([""]); 父子组件传值
父:@submit="自定义的函数"
- 运行时声明
子:const emit = defineEmits(['submit']); emit('submit',{params1:'1'})
// 子
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
// 父
<MyButton @increase-by="(n) => count += n" />
或
<MyButton @increase-by="increaseCount" />
所有传入 $emit() 的额外参数都会被直接传向监听器。举例来说,$emit('foo', 1, 2, 3) 触发后,
监听器函数将会收到这三个参数值。
// 组合式api
<script setup>
const emit = defineEmits(['submit'])
function buttonClick() {
emit('submit',{params1:'1',params2:'2'});
}
</script>
// 选项式api
export default {
emits: ['submit'],
setup(props, ctx) {
ctx.emit('submit')
}
}
- 类型声明式
const emits = defineEmits<{(e: 'submit', data: number): void }>();
const emits = defineEmits<{
(e: 'submit', data: number): void // 函数类型 当传递的值的类型不是number时会报错
(e: 'parentChang'): void // 函数类型
}>();
Provide Inject 依赖注入 任意组件传值
provide('key', { value }); const { value} = inject('key');11
provide() 接受两个参数:
第一个参数是要注入的 key,可以是一个字符串或者一个 symbol;
第二个参数是要注入的值。
inject() 可以接受三个参数:
第一个参数是注入的 key,
第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。
第三个参数是可选的,类型为布尔值。当第二个参数的值就是一个函数,而不是工厂函数时,需要使用将值设置为 false。
配合响应性
当使用响应式 provide/inject 值时,建议尽可能将任何对响应式状态的变更都保持在 provider 内部。
这样可以确保 provide 的状态和变更操作都在同一个组件内,使其更容易维护。
// 父组件
import childVue from './child.vue';
import { ref, provide } from 'vue';
const num = ref(0);
const onClick = () => {
num.value++;
};
provide('count', { num }); // key 值
// 孙组件
import { inject } from 'vue';
const { num } = inject('count');