目录
注释:
一、API风格
1.1、选项式API
1.2、组合式API
二、setup和ref的基本使用
三、reactive的使用
四、vue3响应式的核心原理
五、setup的细节
六、reactive和ref的细节问题
七、计算属性与监视
八、Vue 3 中的生命周期
九、自定义hook函数
十、toRefs的使用
十一、ref的另一个作用:获取页面中的元素
十二、shallowReactive和shallowRef
十三、readonly和shallowReadonly
十四、toRaw和markRaw
十五、 toRef的使用及特点
十六、customRef的使用
十七、响应式数据的判断方法
十八、手写组合API
注释:
1、本文的特点?
? ? ? 本文只列出Vue 3 中的新知识,且所有知识都写在下面代码中的注释里,大家可以结合这些代码和注释去学习这些新知识。
2、适合哪些人学习?
比较适合那些掌握Vue 2 和 基础TS的人群,这样学习起来就非常轻松。
3、提醒:
? ? ? 我在第一节中是使用的setup语法糖,后面所有的代码都是没有使用setup语法糖的,使用的是setup函数。
? ? ? setup语法糖中,定义的变量在模板中是不需要暴露出去的,在模板中直接使用,大家看到后面就知道是什么意思了。
? ? ? Vue 3 中引入组件是不需要注册的,直接import引入,使用即可。剩下的基本和vue 2中的语法一样。
4、接下来的计划:
? ? ? 准备总结数据库(MySQl)的知识,然后持续发布。
? ? ? 总结Java的知识,然后持续发布。
5、寄语:引用撒贝宁老师的开场白
? ? ? 时光总像“林花谢了春红”,脚步“太匆匆”。在追寻梦想的路上,“何妨吟啸且徐行”,这里有“荡胸生层云”的旷达,有“潇潇雨歇”的怅惋,有“长太息以掩涕兮”的悲悯,也有“悠悠我心”的真情!
? ? ? 韶华易逝,沧海桑田。当古代的文人面对时光的流逝,既有“十年生死两茫茫”的深沉,也有“想当年金戈铁马,气吞万里如虎”的壮志,更有“花有重开日,人无在少年”的劝诫。无论“黄沙百战穿金甲,不破楼兰终不还”的决绝,“人生自古谁无死,留取丹心照汗青”的气节,还是“我自横刀向天笑,去留肝胆两昆仑”的慷慨,历史如尘烟消散,情感凝结成永恒。
? ? ?“非淡泊无以明志,非宁静无以致远”,人生就是一场不断完善自己的旅程,这一路上所有的经历,无论悲喜,都是为了成就一个更完美的自己。“东方欲晓,莫道君行早,踏遍青山人未老,风景这边独好。”你会发现,人生有诗意,永远是少年!
一、API风格
Vue的组件可以按两种不同的风格书写:选项式API和组合式API。
1.1、选项式API
Vue 2 中使用的式选项式API,我们可以用包含多个选项的对象来描述组件的逻辑,例如data、methods和mounted。选项式所定义的属性都会暴露在函数的this上,它会指向当前的组件实例。
选项式API代码:
<template>
<div>
<span>{{mag}}</span><br>
<button @click="clickAddFun">+1</button>
</div>
</template>
<script>
export default {
// data () 返回的属性将会成为响应式的状态
// 并且暴露在 this 上
data () {
return {
msg: 0
}
},
// vue 2 中的生命周期钩子
// 这个函数就会在组件挂载完成后被调用
mounted () {
console.log(this.msg);
},
// methods是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件监听器绑定
methods: {
clickAddFun () {
this.msg++;
}
}
}
</script>
<style>
</style>
1.2、组合式API
vue 3 中使用的是组合式API,组合式API的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对vue的响应式系统有更深的理解才能高效使用。相应的,他的灵活性也使得组织和重用逻辑的模式更加强大。
通过组合式API,我们可以使用导入的API函数来描述组件逻辑。在单文件组件中,组合式API通常会与<script setup>搭配使用。这个setup是一个标识,告诉Vue需要在编译时进行一些处理,让我们可以更简洁地使用组合式API。比如,<script setup>中导入和顶层变量/函数都能够模板中直接使用。
组合式API代码:
<template>
<span>{{msg}}</span><br>
<button @click="clickAddFun">+1</button>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 定义一个number类型的响应式变量
const msg: number = ref(0);
// 用来修改状态、触发更新的函数
function clickAddFun ():number {
return msg.value++;
}
// vue 3 中的生命周期钩子
// 这个函数就会在组件挂载完成后被调用
onMounted (() => {
console.log(msg);
})
</script>
<style>
</style>
以上就是组合式API和响应式API的对比,它们的功能是一模一样的,vue 2 中使用的JS,vue 3 中使用的是TS。
二、setup和ref的基本使用
<template>
<h3>{{num}}</h3><br>
<button @click="updateNumFun">+1</button>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'App',
// setup是组合式API的入口函数,只在初始化执行一次
// 一般都是返回一个对象,对象中的属性或方法,模板中可以直接使用
// ref是一个函数
// 作用:定义一个响应式的数据,返回的是一个Ref对象,对象中有一个value值,如果要对数据进行操作,就需要使用该对象调用value属性,进行数据的操作
// 一般是用来定义基本类型的响应式数据
setup () {
// num的类型是Ref类型
// html模板中是不需要使用.value的
const num = ref(0);
// 方法
function updateNumFun () {
console.log(num);
// num是一个Ref对象,对象是不能进行++操作的
num.value++;
}
// 返回时一个对象
return {
// 属性
num,
// 方法
updateNumFun
}
}
});
</script>
<style>
</style>
三、reactive的使用
<template>
<span>{{person.name}}今年{{person.age}}岁了,性别{{person.sex}}</span><br>
<span>{{person.girl.name}}今年{{person.girl.age}}岁了,住在{{person.girl.address}}</span><br>
<button @click="getMessageFun">改变信息</button>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
name: 'App',
// reactive
// 作用:定义多个数据的响应式
// const proxy = reactive(obj):接收一个普通对象然后返回该普通对象的响应式代理器对象
// 响应式转换是"深层次的":会影响对象内部所有嵌套的属性内部基于ES6的proxy实现,通过代理对象操作源对象内部数据都是响应式的
setup () {
// const obj: any = { // 为了使用obj.sex = '男'不出现错误提示信息
const obj = {
name: '王昭没有君啊',
age: 22,
girl: {
name: '王子夭',
age: 3
}
}
// 把数据变成响应的数据
// 返回的是一个proxy的代理对象,被代理的目标对象就是obj对象
// person是代理对象,obj是目标对象
// person的类型是proxy类型
// 直接使用目标对象是不能更新目标对象的属性的,只能通过代理对象来更新数据
const person = reactive<any>(obj)
function getMessageFun () {
// obj.age++; // 无变化,也就是不是响应式的
// person.age++;
// person.girl.age++;
console.log(person);
// person对象或者obj对象添加一个新的属性,哪一种方式会影响页面的更新
// obj.sex = '男'; // 这种方式页面没有更新渲染
person.sex = '男'; // 页面可以更新渲染,这个数据最终也添加到了obj对象
// person对象或者obj对象移出一个属性,哪一种方式会影响页面的更新
// delete obj.age; // 页面没有更新渲染,obj中确实没有了age这个属性
// delete person.age; // 页面跟新渲染,obj中也没有了age这个属性
// 总结:操作代理对象页面会发生渲染,且目标对象的数据也会随之变化
person.girl.address = '武汉';
}
return {
person,
getMessageFun
}
}
});
</script>
<style>
</style>
四、vue3响应式的核心原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// vue3响应式的核心原理
// 通过Proxy(代理):拦截对data任意属性的任意操作(handler处理器对象,有13种方法),比如说属性值的读写、属性的删除、属性的添加等
// 通过Reflect(反射):动态对被代理对象的属性进行特定的操作
// 定义一个目标对象
const obj = {
name: '王昭没有君啊',
age: 22,
children: {
name: '王子夭',
age: 3
}
}
// 把目标对象变为代理对象
// 参数1:obj target目标对象
// 参数2:handler 处理器对象用来监视数据,及实现数据的操作
const proxyObj = new Proxy(obj, {
// handler里的get方法,用来获取目标对象中的数据
// 参数1:target目标对象
// 参数2:propetry要获取的属性
get (target, propetry) {
return Reflect.get(target, propetry); // 通过Reflect里的get方法再把想要获取的数据反射出去
},
// handler里的set方法,用来给目标对象设置()添加数据或者修改数据的
// 参数1:target目标对象
// 参数2:propetry要设置或者修改的属性
// 参数3:value要设置或者修改的属性值
set (target, propetry, value) {
return Reflect.set(target, propetry, value); // 通过Reflect里的set方法再把想要获取的数据反射出去
},
// handler里的deleteProperty方法,用来删除目标对象中的数据
// 参数1:target目标对象
// 参数2:propetry要删除的属性
deleteProperty (target, propetry) {
return Reflect.deleteProperty(target, propetry); // 通过Reflect里的deleteProperty方法把删除后的数据反射出去
}
})
// 想要获得name属性值
// 第一步:通过handler里的get方法,获取到对应的数据
// 第二步:然后再通过Reflect把想要获取的数据反射出去
console.log(proxyObj.name); // 王昭没有君啊
// 设置addresss属性
// 第一步:通过handler里的set方法,设置对应的数据
// 第二步:然后再通过Reflect把设置的数据反射出去
proxyObj.address = '武汉';
console.log(obj); // {name: '王昭没有君啊', age: 22, address: '武汉', children: {name: '王子夭', age: 3}}
// 修改name属性
// 第一步:通过handler里的set方法,设置对应的数据
// 第二步:然后再通过Reflect把设置的数据反射出去
proxyObj.children.name = '王子苓';
proxyObj.children.age = 1;
console.log(obj); // {name: '王昭没有君啊', age: 22, address: '武汉', children: {name: '王子苓', age: 1}}
// 删除一维对象中的address属性,删除二维对象中的age属性
// 第一步:通过handler里的deleteProperty方法,设置对应的数据
// 第二步:然后再通过Reflect把删除后的数据反射出去
delete proxyObj.address;
delete proxyObj.children.age;
console.log(obj); // {name: '王昭没有君啊', age: 22, children: {name: '王子苓'}}
// 由此可见proxy确实也是深度监听的
</script>
</body>
</html>
五、setup的细节
<template>
<span>{{age}}</span><br>
<span>{{num}}</span>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
export default defineComponent({
name: 'App',
setup (props, context) {
// setup的两个参数
// props参数是一个proxy对象,里面有父组件传递的数据,并且是在子组件中使用props接收到的所有的属性,包含props配置声明且传入了所以属性的对象
console.log(props);
// context是一个proxy对象,里面有attrs对象(获取当前组件标签上所有属性的对象,但是该属性是在props中没有声明接收的所有的属性对象,相当于this.$attrs),emit方法(分发事件的),slot对象(插槽)
console.log(context);
const age = ref(3);
const setupFun = () => {
console.log('setup中的方法');
}
console.log('setup执行了', this); // 先执行
// setup的执行时机
// setup先执行,beforeCreate后执行
// 1、说明:setup是在beforeCreate生命周期之前就执行了,而且就执行一次,那也就意味着setup执行的时候,当前的组件还没有创建出来,所以组件实例对象this根本就不能使用
// 2、this是undefined,也就意味着不能通过this再去调用props/data/computed/methods的相关内容
// 3、其实所有的composition API相关回调函数中都不可以
// setup的返回值
// 1、setup返回值是一个对象,内部的属性和方法是可以直接给html模板使用的
// 2、setup中的对象内部的属性和data函数中的属性都可以再html模板中使用
return {
age,
setupFun
}
},
data () {
return {
num: 3,
}
},
beforeCreate () {
console.log('beforeCreate执行了'); // 后执行
},
mounted () {
console.log(this);
// this是一个proxy对象,在里面明显可以看到target对象里面有两个属性age和num,也有两个方法setupFun和methodsFun
// 这也就说明:
// 3、setup中的属性会和data函数中的对象中的属性合并为组件对象的属性
// 4、setup中的方法会和methods对象中的方法合并为组件对象的属性
// 注意:
// 1、在vue3中不要混合使用data和setup,也不要混合只有methods和setup(尽管它是向下兼容的,我这里只是演示用的代码)
// 2、在setup方法中是不能访问data和methods的,因为你要想访问data和methods,就必须要使用this,但是在vue3中我们是不能使用this的
// 3、setup不能是一个async函数:因为返回值不再是return对象,而是一个promise,模板看不到return对象中的属性数据
},
methods: {
methodsFun () {
console.log('methods中的方法');
}
}
});
</script>
<style>
</style>
六、reactive和ref的细节问题
<template>
<span>name: {{name}}</span><br>
<span>child: {{child}}</span><br>
<span>person: {{person}}</span><br>
<button @click="clickUpdateFun">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from 'vue';
export default defineComponent({
name: 'App',
// 1、reactive和ref是vue3中composition API中两个最重要的响应式API
// 2、ref是用来处理基本类型数据,reactive用来处理引用类型数据(深度响应式)
setup () {
const name = ref('王昭没有君啊');
const child = reactive({
name: '王子夭',
age: 3,
hobby: {
sports: '足球'
}
});
// 3、ref也是可以用来设置对象、数组的,但是内部会自动将对象、数组转为reactive的代理对象
// 这也我们一般不用ref设置对象的原因
const person = ref({
name: '王子苓',
age: 1,
hobby: {
sports: '羽毛球'
}
})
// 更新数据,以下的的都能成功改变
function clickUpdateFun () {
console.log(person); // 经过了reactive的处理,形成了一个proxy类型的对象
console.log(person.value.hobby); // 也是一个proxy类型的对象
name.value = '王昭君';
child.age = 2;
child.hobby.sports = '乒乓球';
person.value.age = 2;
person.value.hobby.sports = '篮球'
}
// 4、ref内部:通过value属性添加getter/setter来实现对数据的劫持
// 5、reactive内部:通过使用proxy来实现对象内部所有的数据劫持,并通过Reflect操作对象内部数据
// 6、ref的数据操作:在js中需要.value,在模板中不需要(内部解析模板时会自动添加.value)
return {
name,
child,
person,
clickUpdateFun
}
}
});
</script>
<style>
</style>
七、计算属性与监视
<template>
<h4>信息录入</h4><br>
姓名:<input type="text" v-model="person.name" placeholder="请输入姓名"><br>
年龄:<input type="text" v-model="person.age" placeholder="请输入年龄"><br>
<h4>计算属性和监视的演示</h4>
姓名:<input type="text" v-model="firstName"><br>
姓名:<input type="text" v-model="secondName"><br>
姓名:<input type="text" v-model="thirdName"><br>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, computed, watch, watchEffect } from 'vue';
export default defineComponent({
name: 'App',
setup () {
const person = reactive({
name: '王昭没有君啊',
age: 22
});
// 通过计算属性实现第一个姓名的显示
// 需求:上面录入信息发生变化,第一个姓名显示就发生变化
// 计算属性中的函数只传入一个回调函数的话,就表示是get
// 返回的是一个Ref类型的对象
const firstName = computed(() => {
return person.name;
})
// 通过计算属性实现第二个姓名的显示
// 需求:上面录入信息发生变化,第二个姓名显示就发生变化,并且我们在input框中修改第二个姓名,上面录入的信息也会相应发生变化
// computed要想有get和set操作就必须要传入一个对象
const secondName = computed({
get () {
return person.name;
},
set (val:string) {
person.name = val;
}
})
// 通过监听实现第三个姓名的显示
const thirdName = ref('');
watch (person, (person) => {
thirdName.value = person.name;
},{immediate: true,deep: true})
// immediate 默认会执行一次watch监听
// deep开启深度监视
// watchEffect也是监视,不要配置immediate,本身默认就会进行监视(默认执行一次watch监听)
// watchEffect(() => {
// thirdName.value = person.name;
// })
// watchEffect监视thirdName,在input框中修改第三个姓名,上面录入的信息也会相应发生变化
watchEffect(() => {
person.name = thirdName.value;
})
// watch是可以监听多个数据的
// watch([person.name, person.age, thirdName], () => {
// 只有thirdName发生变化了,才会打印111
// 因为person.name, person.age不是响应式的数据
console.log(111);
// })
// 要使用watch监视非响应式的数据的时候,代码需要改动
watch([() => person.name, () => person.age, thirdName], () => {
console.log(111);
})
return {
person,
firstName,
secondName,
thirdName
}
}
});
</script>
<style>
</style>
八、Vue 3 中的生命周期
<template>
<h3>vue3中的生命周期</h3>
</template>
<script lang="ts">
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount,onUnmounted } from 'vue';
export default defineComponent({
name: 'App',
// 1、vue2的生命周期 vue3的生命周期
// beforeCreate setup
// created setup
// beforeMount onBeforeMount
// mounted onMounted
// beforeUpdate onBeforeUpdate
// updated onUpdated
// beforeDestroy onBeforeUnmount
// destroyed onUnmounted
// 2、vue3中使用setup替代了vue2中的beforeCreate、created
// 3、vue2中的beforeDestroy、destroyed在vue3中已经改了名字,改为了onBeforeUnmount、onUnmounted
// 4、vue3中生命周期的使用和vue2中的使用场景一模一样(我在《vue框架》(详细讲解vue2)这篇文章中已经详细讲解过了生命周期的具体内容,有想要了解生命周期的可以去看一下这篇文章,这里不作过多介绍了)
setup () {
// 挂载前
onBeforeMount (() => {
}),
// 挂载后
onMounted (() => {
}),
// 更新前
onBeforeUpdate (() => {
}),
// 更新后
onUpdated (() => {
}),
// 销毁前
onBeforeUnmount (() => {
}),
// 销毁后
onUnmounted (() => {
})
return {
}
}
});
</script>
<style>
</style>
九、自定义hook函数
// 自定义hook函数
// 1、自定义hook函数的作用:是用来在vue3中封装一些可复用功能函数的
// 2、自定义hook函数的优势:很清楚复用功能代码的来源,更清楚易懂
// 举例:自定义一个hook函数,将其写入hook.ts文件中,这样你就可以在你的任何页面中调用这个公共方法了
import { reactive } from 'vue'
export default function () {
interface Person {
name: string;
age: number;
}
const person: Person = reactive({
name: '王昭没有君啊',
age: 22
})
return person;
}
十、toRefs的使用
<template>
<span>name:{{name}}</span><br>
<span>age:{{age}}</span><br>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
name: 'App',
// toRefs:可以把一个响应式的对象转换为普通对象,该对象中的每一个属性都是一个ref
// 它会把你结构出来的值通过toRefs包裹后还是响应式的
setup () {
const person = reactive({
name: '王昭没有君啊',
age: 22
})
// 写法一
//const state = toRefs(person);
// 写法二
const {name, age} = toRefs(person);
setInterval(() => {
// 使用这种方式state.age.value++,修改age属性
// 说明此时的age就是一个ref对象
// 写法一
// state.age.value++;
// 写法二
age.value++;
}, 1000)
// 从打印的结果上来看,也都是ref类型的对象
// console.log(state);
return {
// 写法一
//...state,
// 写法二
name,
age
}
}
});
</script>
<style>
</style>
十一、ref的另一个作用:获取页面中的元素
<template>
<input type="text" ref="input">
</template>
<script lang="ts">
import { defineComponent, ref, onMounted} from 'vue';
export default defineComponent({
name: 'App',
// 当页面加载完毕后,页面文本框自动获取焦点
setup () {
// 默认为空,当页面加载完毕后,说明组件已经存在,然后获取文本框元素
const input = ref<HTMLElement | null>(null);
onMounted(() => {
if (input.value) {
console.log(input.value);
input.value.focus()
}
})
return {
input
}
}
});
</script>
<style>
</style>
十二、shallowReactive和shallowRef
<template>
<span>{{person1}}</span><br>
<span>{{person2}}</span><br>
<span>{{person3}}</span><br>
<span>{{person4}}</span><br>
<button @click="updateFun">更改数据</button>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, shallowReactive, shallowRef } from 'vue';
export default defineComponent({
name: 'App',
// 1、shallowReactive:只处理了对象内外层属性的响应式(浅响应)
// 2、shallowRef:只处理了value的响应式,不进行对象的reactive处理(浅响应)
// 什么时候用浅响应式呢?
// 一般情况下使用ref和reactive即可
// 如果有一个对象数据,结构比较深,但变化只是需要外层属性变化,使用shallowReactive
// 如果一个对象数据,后面会产生新的对象来替代,使用shallowRef
setup () {
// reactive深度响应
const person1 = reactive({
name: '王昭没有君啊',
age: 22,
hobby: {
sports: '足球'
}
})
// shallowReactive浅响应
const person2 = shallowReactive({
name: '王昭君',
age: 22,
hobby: {
sports: '足球'
}
})
// ref深响应
const person3 = ref({
name: '王子夭',
age: 3,
hobby: {
sports: '足球'
}
})
// shallowRef浅响应
const person4 = shallowRef({
name: '王子苓',
age: 1,
hobby: {
sports: '足球'
}
})
function updateFun () {
// 数据都发生改变
// person1.age++;
// person1.hobby.sports = '观看足球比赛'
// 只有年龄发生变化
// person2.age++;
// person2.hobby.sports = '观看足球比赛'
// 数据都发生改变
// person3.value.age++;
// person3.value.hobby.sports = '观看足球比赛'
// 只有年龄发生变化
// person4.value.age++;
// person4.value.hobby.sports = '观看足球比赛'
}
return {
person1,
person2,
person3,
person4,
updateFun
}
}
});
</script>
<style>
</style>
十三、readonly和shallowReadonly
<template>
<span>{{state2}}</span>
<button @click="updateFun">更改数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, readonly, shallowReadonly } from 'vue';
export default defineComponent({
name: 'App',
// 1、readonly:深度只读数据,对象中的数据都是只读的,无法修改
// 2、shallowReadonly:浅度只读数据,对象中外层数据是只读的,无法修改,但是深层次的数据是可以修改的
setup () {
const person = reactive({
name: '王昭没有君啊',
age: 22,
hobby: {
sports: '足球'
}
})
const state = readonly(person);
const state2 = shallowReadonly(person);
function updateFun () {
// 报错,因为readonly是只读的数据,而且是深度只读
// state.name = '王昭君';
// state.hobby.sports = '观看足球比赛';
// 报错,因为shallowReadonly是只读的数据
// state2.name = '王昭君';
// 修该成功,说明shallowReadonly是浅只读
state2.hobby.sports = '观看足球比赛';
}
return {
person,
state,
state2,
updateFun
}
}
});
</script>
<style>
</style>
十四、toRaw和markRaw
<template>
<span>{{person}}</span><br>
<button @click="clickFun1">测试toRaw</button><br>
<button @click="clickFun2">测试markRaw</button><br>
</template>
<script lang="ts">
import { defineComponent, reactive, toRaw, markRaw } from 'vue';
export default defineComponent({
name: 'App',
// 1、toRaw:toRaw会把代理对象变为普通对象,数据变化,页面不变化
// 2、markRaw:markRaw标记的对象数据,从此以后都不能再成为代理对象了,只能返回对象本身
setup () {
interface Person {
name: string;
age: number;
hobby?: string;
}
const person: Person = reactive({
name: '王昭没有君啊',
age: 22
})
function clickFun1 () {
// 页面不发生变化
// 这是因为toRaw把代理对象变为普通对象
const toRawData = toRaw(person);
toRawData.name = '王昭君';
}
function clickFun2 () {
// 多次点击改变数据,页面只发生了一次变化
// 这是因为markRaw标记的对象数据,从此以后都不能再成为代理对象了,只能返回对象本身
const markRawData = markRaw(person);
markRawData.hobby += '观看足球比赛';
}
return {
person,
clickFun1,
clickFun2
}
}
});
</script>
<style>
</style>
十五、 toRef的使用及特点
<template>
<span>{{person}}</span><br>
<span>{{age}}</span><br>
<span>{{name}}</span><br>
<button @click="updateFun">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, toRef, ref } from 'vue'
export default defineComponent({
name: 'App',
setup () {
const person = reactive({
name: '王昭没有君啊',
age: 22
})
// 把响应式数据person对象中的某个属性age变为了ref对象
const age = toRef(person, 'age');
// 把响应式对象中的某个属性使用ref进行包装,变成了一个ref对象
const name = ref(person.name);
// 打印出来都是ref对象
console.log(age);
console.log(name);
// 更新数据
const updateFun = () => {
// 修改person里的age,不仅person会变化,toRef转化为的age也跟着变化
// person.age += 1;
// 修改toRef转化为的age,person里的age也会发生变化
// 说明toRef和person是有关联的,也可以理解为双向绑定的,会互相影响其变化
// age.value += 1;
// 修改ref包装后的属性,只有name发生变化,而person不会发生变化
// 说明ref只是拷贝了一份person
// name.value += '啊';
}
return {
person,
age,
name,
updateFun
}
}
});
</script>
<style>
</style>
十六、customRef的使用
<template>
<input type="text" v-model="value"><br>
<span>{{value}}</span>
</template>
<script lang="ts">
import { defineComponent, customRef } from 'vue'
// customRef:用于自定义一个ref,可以显示的控制以来追踪和出发响应,接受一个工厂函数,两个参数分别用于追踪的track与用于触发的tigger,并返回与一个带有set和get属性的参数
// ref和customRef的区别:
// ref响应式数据本身是立即响应的,而customRef它是可以自定义响应时间的
// 自定义hook防抖函数
// value传入的数据的类型不确定,要用泛型,time是防抖的时间间隔,默认是500ms
function setTimeFun<T>(value: T, time = 500) {
let timeID: number;
return customRef ((track, tigger) => {
return {
// 返回数据
get () {
// 告诉vue追踪数据
track ()
return value;
},
// 设置数据
set (newValue: T) {
// 清除定时器
clearTimeout(timeID);
// 开启定时器
timeID = setTimeout(() => {
value = newValue;
// 告诉vue更新页面
tigger();
}, time)
}
}
})
}
export default defineComponent({
name: 'App',
setup () {
// const value = ref('abc');
const value = setTimeFun('abc', 1000);
return {
value
}
}
});
</script>
<style>
</style>
十七、响应式数据的判断方法
<template>
<h4>响应式数据的判断方法</h4>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, readonly, isRef, isReactive, isReadonly, isProxy } from 'vue'
export default defineComponent({
name: 'App',
setup () {
// isRef:检查一个值是否为一个ref对象
console.log(isRef(ref({}))); // true
// isReactive:检查一个对象是否是由reactive创建的响应式代理
console.log(isReactive(reactive({}))); // true
// isReadonly:检查一个对象是否是由readonly创建的只读代理
console.log(isReadonly(readonly({}))); // true
// isProxy:检查一个对象是否由reactive或者readonly方法创建的代理
console.log(isProxy(reactive({}))); // true
console.log(isProxy(readonly({}))); // true
return {
}
}
});
</script>
<style>
</style>
十八、手写组合API
index.js文件代码:
// 手写shallowReactive(浅监听)和reactive(深监听)
// 第二步:定义一个handler处理对象
const handler = {
// 获取数据
get (target, prop) {
console.log(prop);
if (prop === '_is_reactive') {
return true;
}
console.log('执行了get函数');
return Reflect.get(target, prop)
},
// 设置数据/修改数据
set (target, prop, value) {
console.log('执行了set函数');
return Reflect.set(target, prop, value);
},
// 删除数据
deleteProperty (target, prop) {
console.log('执行了deleteProperty函数');
return Reflect.deleteProperty(target, prop);
},
}
// 第一步:定义一个shallowReactive函数,传入一个目标对象
function shallowReactive (target) {
// 判断传入的目标对象是不是Object类型(数组,对象)
if (target && typeof target === 'object') {
return new Proxy(target, handler)
}
// 如果传入的数据是基本数据类型,就直接返回
return target
}
// 第一步:定义一个reactive函数,传入一个目标对象
function reactive (target) {
// 判断传入的目标对象是不是Object类型(数组,对象)
if (target && typeof target === 'object') {
// 对数组或者对象中所有的数据进行reactive的递归处理
// 如果传入的数据是数组
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, handler)
}
// 如果传入的数据是基本数据类型,就直接返回
return target
}
// 手写shallowReadonly(浅只读)和readonly(深只读)
// 第二步:定义一个handler处理对象
const readHandler = {
get (target, prop) {
if (prop === '_is_readonly') {
return true;
}
console.log('执行了读取数据');
return Reflect.get(target, prop);
},
set (target, prop, value) {
console.warn('只能读取数据,不能修改或者添加数据');
return true;
},
deleteProperty (target, prop) {
console.warn('只能读取数据,不能删除数据');
return true;
},
}
// 第一步:定义一个shallowReadonly函数
function shallowReadonly (target) {
if (target && typeof target === 'object') {
return new Proxy(target, readHandler);
}
return target
}
// 定义一个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(keys => {
target[keys] = readonly(target[keys]);
})
}
return new Proxy(target, readHandler);
}
return target
}
// 手写shallowRef和ref
// 定义一个shallowRef函数
function shallowRef (target) {
return {
// 把target数据保存起来
_value: target,
get value () {
console.log('执行了get函数');
return this._value;
},
set value (val) {
console.log('执行了set函数');
return this._value = val
}
}
}
// 定义一个ref函数
function ref (target) {
target = reactive(target);
return {
_is_ref: true, // 标识当前对象是ref对象
// 把target数据保存起来
_value: target,
get value () {
console.log('执行了get函数');
return this._value;
},
set value (val) {
console.log('执行了set函数');
return this._value = val;
}
}
}
// 定义一个函数isRef,判断当前对象是不是ref对象
function isRef (obj) {
return obj && obj._is_ref;
}
// 定义一个函数isReactive,判断当前对象是不是reactive对象
function isReactive (obj) {
return obj && obj._is_reactive;
}
// 定义一个函数isReadonly,判断当前对象是不是readonly对象
function isReadonly (obj) {
return obj && obj._is_readonly;
}
// 定义一个函数isRroxy,判断当前对象是不是reactive对象或者redonly对象
function isProxy (obj) {
return isReactive (obj) || isReadonly (obj);
}
index.html文件代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./index.js"></script>
<script type="text/javascript">
// 测试 手写的shallowReactive和reactive
const person = shallowReactive({
name: '王昭没有君啊',
hobby: {
sports: '足球'
}
})
// 执行了get函数,执行了set函数
// person.name += '啊';
// 只执行了get函数
// person.hobby.sports = '观看足球比赛';
// 执行了get函数,执行了deleteProperty函数
// delete person.name;
// 只执行了get函数
// delete person.hobby.sports;
// 说明当前是浅劫持
const state = reactive({
name: '王昭没有君啊',
hobby: {
sports: '足球'
}
})
// 执行了get函数,执行了set函数
// state.name += '啊';
// 执行了get函数,执行了set函数
// state.hobby.sports = '观看足球比赛';
// 执行了get函数,执行了deleteProperty函数
// delete state.name;
// 执行了get函数,执行了deleteProperty函数
// delete state.hobby.sports;
// 说明当前是深劫持
// 测试 手写的shallowReadonly和readonly
const person2 = shallowReadonly({
name: '王昭没有君啊',
hobby: ['足球', '编程']
})
// 执行了读取数据
// console.log(person2.name);
// 只能读取数据,不能修改或者添加数据
// person2.name = '王昭没有君啊';
// 只能读取数据,不能删除数据
// delete person2.name;
// 执行了读取数据
// 只读的,但是不可以深层次的修改
// console.log(person.hobby[1] = '观看足球比赛');
// 只读的,但是不可以深层次的删除
// delete person.hobby[1];
// 说明是浅劫持
const state2 = readonly({
name: '王昭没有君啊',
hobby: ['足球', '编程']
})
// 执行了读取数据
// console.log(state2.name);
// 只能读取数据,不能修改或者添加数据
// state2.name = '王昭没有君啊';
// 只能读取数据,不能删除数据
// delete state2.name;
// 执行了读取数据
// 只读的,可以深层次的修改
// console.log(state2.hobby[1] = '观看足球比赛');
// 只读的,可以深层次的删除
// delete state2.hobby[1];
// 说明是深劫持
// 测试 手写的shallowRef和ref
const person3 = shallowRef({
name: '王昭没有君啊',
hobby: {
sports: '足球'
}
})
// 执行了get函数
// console.log(person3.value);
// 执行了set函数
// person3.value = '';
// 只执行了get函数
// person3.value.hobby = '';
// 说明是浅劫持
const state3 = ref({
name: '王昭没有君啊',
hobby: {
sports: '足球'
}
})
// 执行了get函数
// console.log(state3.value);
// 执行了set函数
// state3.value = '';
// 执行了get函数,执行了set函数
// state3.value.hobby = '1';
// 说明是深劫持
console.log(isRef(ref({})));
console.log(isReactive(reactive({})));
console.log(isReadonly(readonly({})));
console.log(isProxy(reactive({})));
console.log(isProxy(readonly({})));
</script>
</body>
</html>
|