在书写
vue3的过程中,我们初步了解了vue3的新特性,今天,我们将延续Composition API的话题,来了解Composition API的原理⬇⬇⬇
<-- 首先我们来看第一个 -->
customRef
- customRef用于自定义一个ref,可以显示的控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别用于追踪的track与用于触发响应的triger,并返回一个带有get和set属性的对象 看到这个,我们会不会在我们平时应用中想起这个组合api在防抖中会有将极大地帮助,可以很简单的实现防抖效果,下面我们来看:
封装hook函数:
import {customRef} from 'vue';
export default function anti_shake<T>(value: T, delay = 200) {
let timeOut: number
//track 追踪数据
//trigger 更新界面
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue: T) {
clearTimeout(timeOut)
timeOut = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
引入封装好的hook函数⬇⬇⬇
<script lang="ts">
import {defineComponent, ref} from 'vue';
import anti_shake from "@/hooks/anti_shake";
export default defineComponent({
name: 'father',
setup(props) {
const keyNum = anti_shake('', 1000)
return {
keyNum
}
}
})
</script>
以上就是我们运用customRef实现防抖的效果。🤭 🤭 🤭
provide 与 inject运用
- provide
和inject提供依赖注入,功能类似 2.x 的provide/inject - 实现跨层级组件(祖孙)间通信
provide和inject可以不经过父级组件直接进行爷爷组件与孙子组件之间的通讯,下面我们来看,
父组件:
<template>
<p>当前颜色: {{color}}</p>
<button @click="color='red'">红</button>
<button @click="color='yellow'">黄</button>
<button @click="color='blue'">蓝</button>
<Son />
</template>
<script lang="ts">
import { provide, ref, defineComponent} from 'vue'
import Son from './Son.vue'
export default defineComponent({
name: 'ProvideInject',
components: {
Son
},
setup() {
const color = ref('red')
provide('color', color)
return {
color
}
}
})
</script>
子组件:
<template>
<GrandSon />
</template>
<script lang="ts">
import {defineComponent} from 'vue'
import GrandSon from './GrandSon.vue'
export default defineComponent({
components: {
GrandSon
},
})
</script>
孙子组件:
<template>
<h3 :style="{color}">孙子组件: {{color}}</h3>
</template>
<script lang="ts">
import { inject, defineComponent } from 'vue'
export default defineComponent({
setup() {
const color = inject('color')
return {
color
}
}
})
</script>
看上面,我们可以直接将父级组件中的color属性传递给孙子组件使用,实现跨层级组件(祖孙)间通信。
判断响应式函数
我们可以通过下面的这些组合api快速的判断属性是否是响应式的,从而提高代码的书写速度。。。
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly创建的只读代理 - isProxy: 检查一个对象是否是由
reactive或者readonly方法创建的代理
手写组合api
reactive和shallowReactive
首先,我们要理解两这个区别,shallowReactive可以说是浅劫持,而reactive是深劫持,因此,下面我们从浏览器角度来理解两个组合api的原理。
<script lang="ts">
import {defineComponent} from 'vue';
// reactiveHandler 处理器
const reactiveHandler = {
// target 对象 prop 属性
get(target, prop) {
const result = Reflect.get(target, prop)
console.log('拦截读取数据', target, prop)
return result
},
set(target, prop, value) {
const result = Reflect.set(target, prop, value)
console.log('拦截修改数据', target, prop, value)
return result
},
deleteProperty(target, prop) {
const result = Reflect.deleteProperty(target, prop)
console.log('拦截删除数据', target, prop)
return result
}
}
function shallowReactive<T>(target: T): T {
if (target && typeof target === 'object') {
return new Proxy(target, reactiveHandler)
}
return target
}
function reactive<T>(target: T): T {
if (target && typeof target === 'object') {
// 判断当前数据是数组还是对象对象(递归处理)
if (Array.isArray(target)) {
target.forEach((item, index) => {
target[index] = reactive(item)
})
} else {
Object.keys(target).forEach(key => {
target[key] = reactive(target[key])
})
}
return new Proxy(target, reactiveHandler)
}
return target
}
export default defineComponent({
name: 'father',
setup(props) {
const people = reactive({
name: '小明',
books: {
content: '红楼梦'
}
})
// 测试 reactive
people.name = '小红'
people.books.content = '水浒传'
delete people.name
delete people.books.content
const people1 = shallowReactive({
name: '小明',
books: {
content: '红楼梦'
}
})
// 测试 shallowReactive
people1.name = '小红'
people1.books.content = '水浒传'
delete people1.name
delete people1.books.content
return {
people,
people1
}
}
})
</script>
测试结果(reactive):
测试结果(shallowReactive):
下面我们继续来看⬇⬇⬇
shallowRef 和 ref
和我们上面说的很相似,shallowRef只会将外层数据变成响应式的数据,而ref可以将最底层的数据变成响应式的,下面我们来看两者的触发原理⬇⬇⬇
<script lang="ts">
import {defineComponent, reactive} from 'vue';
function shallowRef(target) {
const result = {
get value() {
return target
},
set value(val) {
target = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
function ref(target) {
if (target && typeof target === 'object') {
target = reactive(target)
}
const result = {
get value() {
return target
},
set value(val) {
target = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
export default defineComponent({
name: 'father',
setup(props) {
//测试 shallowRef
const shallowRef1 = shallowRef({
a: 'abc',
})
shallowRef1.value = '111'
console.log(shallowRef1.value)
// 测试 ref
const ref1 = ref(0)
const ref2 = ref({
a: 'abc',
b: [{x: 1}],
c: {x: [11]},
})
console.log(ref1.value += 1)
console.log(ref2.value.b[0].x += 1)
return {}
}
})
</script>
测试结果:
由此可见,两者的原理几乎相同,只是深浅程度不同。
⬇ ⬇ ⬇ ⬇ ⬇ ⬇ ⬇ ⬇ ⬇
isRef, isReactive 和 isReadonly
这三个可能以后在写的时候会经常用到,用来判断是不是某个类型的对象, 原理很简单, 我们呢来看一下。
//判断是否是ref对象
function isRef(obj) {
return obj && obj._is_ref
}
//判断是否是reactive对象
function isReactive(obj) {
return obj && obj._is_reactive
}
//判断是否是readonly对象
function isReadonly(obj) {
return obj && obj._is_readonly
}
//是否是reactive或readonly产生的代理对象
function isProxy (obj) {
return isReactive(obj) || isReadonly(obj)
}
上面这个测试我就不写啦,有用到的可以直接使用。🤭🤭🤭
学到现在啦,是不是该放松一下啦!记得往外面看一看,活动一下哈(●ˇ∀ˇ●)
shallowReadonly 和 readonly
还是和上面一样,深浅程度的问题。
<script lang="ts">
import {defineComponent, reactive} from 'vue';
const readonlyHandler = {
get(target, key) {
if (key === '_is_readonly') return true
return Reflect.get(target, key)
},
set() {
console.warn('只读的, 不能修改')
return true
},
deleteProperty() {
console.warn('只读的, 不能删除')
return true
},
}
//shallowReadonly
function shallowReadonly(obj) {
return new Proxy(obj, readonlyHandler)
}
//readonly
function readonly(target) {
if (target && typeof target === 'object') {
// 判断当前数据是数组还是对象(递归处理)
if (Array.isArray(target)) {
target.forEach((item, index) => {
target[index] = readonly(item)
})
} else {
Object.keys(target).forEach(key => {
target[key] = readonly(target[key])
})
}
return new Proxy(target, readonlyHandler)
}
return target
}
export default defineComponent({
name: 'father',
setup(props) {
//测试 readonly
const objReadOnly = readonly({
a: {
b: 1
}
})
//测试 shallowReadonly
const objShallowReadOnly = shallowReadonly({
a: {
b: 1
}
})
console.log(objReadOnly.a = 1)
console.log(objReadOnly.a.b = 2)
console.log(delete objReadOnly.a)
console.log(objShallowReadOnly.a = 1)
console.log(objShallowReadOnly.a.b = 2)
console.log(delete objShallowReadOnly.a)
return {}
}
})
</script>
测试结果:
这些组合api大家都可以看一下,挺重要的,对以后使用vue3做项目会有很大的帮助。。。
新组件
在最后和大家提一点新组件,没事的时候都可以试一下,挺有意思的这几个组件 Fragment(## 片断),Teleport(## 瞬移),Suspense(## 不确定的),这三个是vue3中最新出来的。
Fragment: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中。
Teleport: 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示。
Suspense: 允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验。
拜拜啦!今天的努力,会让你的明天更好哈! 💪💪💪