学习vue3记录
1.toRef
toRef是用来给抽离响应式对象(被reactive包裹的对象)中的某一个属性的,并且把这个属性包裹成ref对象,使其和原对象产生链接。
或许有人就回有人有疑问了?这个toRef存在有什么意义呢?因为我们知道reactive包裹的东西其实已经有了响应式了,再用toRef什么意义呢?
<script setup lang="ts">
import { ref,reactive,toRef } from 'vue';
const userinfo = reactive({
name:'张三',
age:18
})
//定义一个age2赋值userinfo.age
const age2 = userinfo.age
// const state = toRef(userinfo,'age')
const toChange = () =>{
userinfo.age++
console.log("userinfo",userinfo);
}
</script>
<template>
<div class="content">
<button @click="toChange">按钮</button>{{ age2 }}
</div>
</template>
<style scoped>
</style>
定义一个age2赋值userinfo.age,修改对象userinfo里的age值,数据改变DOM不改变
而使用toRef会保持对其源 property 的响应式连接。
并且响应是互相的,数据源数据也会被更改
数据会实时更新到HTML视图上
<script setup lang="ts">
import { ref,reactive,toRef } from 'vue';
const userinfo = reactive({
name:'张三',
age:18
})
const age2 = userinfo.age
//toRef
const state = toRef(userinfo,'age')
const toChange = () =>{
//.value调用
state.value++
console.log("userinfo",userinfo);
}
</script>
<template>
<div class="content">
<button @click="toChange">按钮</button>{{ state }}
</div>
</template>
<style scoped>
</style>
数据更新DOM也同步更新
有人又会问了,那如果说toRef只是把这个响应式对象的属性包裹成ref对象,那我直接用ref这个对象不就可以了吗?何必要用toRef呢。
答:ref是不会去更改原数据的,ref本质是拷贝原数据,而toRef会修改原数据!!
使用ref DOM更新,数据没更新
<script setup lang="ts"> import { ref,reactive,toRef } from 'vue'; const userinfo = reactive({ name:'张三', age:18 }) const age2 = userinfo.age //ref const state = ref(userinfo.age) const toChange = () =>{ //.value调用 state.value++ console.log("userinfo",userinfo); } </script> <template> <div class="content"> <button @click="toChange">按钮</button>{{ state }} </div> </template> <style scoped> </style>
2.toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref*。
这句官方定义我都读了很久没太懂啥玩意儿啊?别怕,我给你解释一下
批量版的toRef
toRef不是只能对象中的一个属性嘛,这个可以直接给你整个对象的属性都一起给整成ref。
<script setup lang="ts"> import { ref,reactive,toRef,toRefs } from 'vue'; const userinfo = reactive({ name:'张三', age:18 }) const age2 = userinfo.age //toRefs const userinfo2 = toRefs(userinfo) const toChange = () =>{ //.value调用 userinfo2.age.value++ console.log("userinfo2",userinfo2); } </script> <template> <div class="content"> <button @click="toChange">按钮</button>{{ userinfo2.age.value }} </div> </template> <style scoped> </style>
和toRef的区别,在使用的时候注意要加上xxx.属性名.value
总结:toRefs和toRef没啥区别,就是批量和单个的区别。也是相互响应
3.toRaw
将响应式对象修改为普通对象
4.computed
<script setup lang="ts"> import { computed } from '@vue/reactivity'; import { reactive } from 'vue'; const author = reactive({ name:'张三', books:[ 'book1', 'book2', 'book3', ] }) const com = computed(()=>{ return author.books.length>0?"Yes":"No" }) </script> <template> <div class="content"> <p>作者:{{ author.name }}</p> 是否有作品:{{ com }} </div> </template> <style scoped> </style>
5.Watch
vue3 watch 的作用和 Vue2 中的 watch 作用是一样的,他们都是用来监听响应式状态发生变化的,当响应式状态发生变化时,就会触发一个回调函数。
watch(data,()=>{},{})
-
参数一,监听的数据
-
参数二,数据改变时触发的回调函数(newVal,oldVal)
-
参数三,options配置项,为一个对象
-
1、监听ref定义的一个响应式数据
<script setup lang="ts"> import {ref, watch} from "vue"; const str = ref('彼时彼刻') // 3s后改变str的值 setTimeout(()=>{ str.value = "恰如此时此刻" },3000) watch(str,(newVal,oldVal)=>{ console.log(newVal,oldVal); }) </script> <template> <div class="content"> {{ str }} </div> </template> <style scoped> </style>
-
2、监听多个ref
这时候写法变为数组的形式
<script setup lang="ts"> import {ref, watch} from "vue"; const name = ref("张三") const age = ref(18) // 3s后改变str的值 setTimeout(()=>{ name.value = "李四" age.value = 19 },3000) watch([name,age],(newVal,oldVal)=>{ console.log(newVal,oldVal); }) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
-
3、监听Reactive定义的响应式对象
<script setup lang="ts"> import {ref, watch,reactive} from "vue"; const infoName = reactive({ name:'张三', age:18 }) setTimeout(()=>{ infoName.name = '李四', infoName.age = 19 },3000) watch(infoName,(newVal,oldVal)=>{ console.log(newVal,oldVal); }) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
tips:
- 监视reactive定义的响应式数据时,oldvalue无法正确获取,强制开启了深度监视(deep配置失败)
- 监视reactive定义的响应式数据中某个属性时:deep配置有效
-
4、监听reactive 定义响应式对象的单一属性
错误写法
<script setup lang="ts"> import {ref, watch,reactive} from "vue"; const infoName = reactive({ name:'张三', age:18 }) setTimeout(()=>{ infoName.name = '李四', infoName.age = 19 },3000) watch(infoName.age,(newVal,oldVal)=>{ console.log(newVal,oldVal); }) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
控制台报错
如果我们非要监听响应式对象中的某个属性,我们可以使用 getter 函数的形式,即将watch第一个参数修改成一个回调函数的形式
正确写法:
<script setup lang="ts"> import {ref, watch,reactive} from "vue"; const infoName = reactive({ name:'张三', age:18 }) setTimeout(()=>{ infoName.name = '李四', infoName.age = 19 },3000) //监听reactive 定义响应式对象的单一属性 watch(()=>infoName.age,(newVal,oldVal)=>{ console.log(newVal,oldVal); }) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
控制台打印
-
5、监听reactive定义的 引用数据
<script setup lang="ts"> import {ref, watch,reactive} from "vue"; const infoName = reactive({ name:'张三', age:18, obj:{ str:"hello Vue3!" } }) setTimeout(()=>{ infoName.obj.str="更改后的文字" },3000) // 需要自己开启 deep:true深度监听,不然不发触发 watch 的回调函数 watch(()=>infoName.obj,(newVal,oldVal)=>{ console.log(newVal,oldVal); },{ deep:true }) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
需要自己开启 deep:true深度监听,不然不发触发 watch 的回调函数
6.WatchEffect
watchEffect函数的作用:
传入的一个函数,当依赖项变化的时候,重新执行改函数。
watchEffect函特性:
与watch相似都可以监听一个数据源。
但是watchEffect会在初始化的时候调用一次,与watch的immediate类似。
会立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。(有点像计算属性)
如果用到 a 就只会监听 a, 就是用到几个监听几个 而且是非惰性,会默认调用一次
<script setup lang="ts"> import {ref, watchEffect} from "vue"; const num = ref(0) setTimeout(()=>{ num.value++ },3000) watchEffect(()=>{ console.log(num.value); }) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
可以在控制台上看到,第一次进入页面时,打印出num 值改变:0
,三秒后,再次打印num 值改变:1
watchEffect 它与 watch 的区别主要有以下几点:
- 不需要手动传入依赖
- 每次初始化时会执行一次回调函数来自动获取依赖
- 无法获取到原值,只能得到变化后的值
<script setup lang="ts"> import {reactive, ref, watchEffect} from "vue"; const num = ref(0) const obj = reactive({ name:'张三', age:18 }) watchEffect(()=>{ console.log(obj.name); console.log(obj.age); }) setTimeout(()=>{ obj.name = '李四', obj.age = 19 },3000) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
-
停止监听
当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
但是我们采用异步的方式创建了一个监听器,这个时候监听器没有与当前组件绑定,所以即使组件销毁了,监听器依然存在。
这个时候我们可以显式调用停止监听
<script setup lang="ts"> import {reactive, ref, watchEffect} from "vue"; const num = ref(0) const obj = reactive({ name:'张三', age:18 }) const res = watchEffect(()=>{ console.log(obj.name); console.log(obj.age); }) setTimeout(()=>{ obj.name = '李四', obj.age = 19 },3000) res() //关闭监听 </script> <template> <div class="content"> </div> </template> <style scoped> </style>
watch
:显示指定依赖源,依赖源更新时执行回调函数。watchEffect
:自动收集依赖源,依赖源更新时候重新执行自身。
- watchEffect 如果存在的话,组件初始化的时候就会自动自行一次,不需要像 watch 一样设置立即执行。
- watch 每次回调之后是可以获取到最新值和上一次的老值,但是 watchEffect 是拿不到的。
- watchEffect 不需要指定监听的属性,他会自动进行依赖收集,只要我们回调中使用了响应式的属性,那么这些属性在变更之后这个回调都会执行,不像 watch 只能监听指定的属性。
- 使用的时候也是需要引入。
- 记住一点哈, watch 可以代替 watchEffect,但是 watchEffect 不能替代 watch。
总结:能用 watch 就不要用 watchEffect。
-
清除副作用(onInvalidate)
watchEffect 的第一个参数——effect函数——可以接收一个参数:叫onInvalidate,也是一个函数,用于清除 effect 产生的副作用
就是在触发监听之前会调用一个函数可以处理你的逻辑,例如防抖
<script setup lang="ts"> import {reactive, ref, watchEffect} from "vue"; const num = ref(0) watchEffect(onInvalidate =>{ console.log(num.value); onInvalidate(()=>{ console.log('执行'); }) }) setTimeout(()=>{ num.value++ },3000) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
控制台依次输出:0 => 执行 => 1
onInvalidate清除副作用函数注意点
1.该函数总是在watchEffect执行的时候再次执行
2.当组件被销毁的时候该函数再次执行
3.该函数总是优先于watchEffect中的同步/异步代码执行
4.Promize函数的执行应该在该函数下面
-
配置选项
watchEffect的第二个参数,用来定义副作用刷新时机,可以作为一个调试器来使用
flush (更新时机):
-
1、pre:组件更新前执行
-
2、sync:强制效果始终同步触发
-
3、post:组件更新后执行
<script setup lang="ts"> import {reactive, ref, watchEffect} from "vue"; const num = ref(0) watchEffect(onInvalidate =>{ console.log(num.value); onInvalidate(()=>{ console.log('执行'); }); },{ flush:"post", //此时这个函数会在组件更新之后去执行 onTrigger(e){ //作为一个调试工具,可在开发中方便测试 console.log('触发',e); } }) setTimeout(()=>{ num.value++ },3000) </script> <template> <div class="content"> </div> </template> <style scoped> </style>
7.生命周期
和 vue2 相比的话,基本上就是将 Vue2 中的beforeDestroy名称变更成beforeUnmount; destroyed 表更为 unmounted;然后用setup代替了两个钩子函数 beforeCreate 和 created;新增了两个开发环境用于调试的钩子
8.父子组件传参
defineProps
父组件传参
<script setup lang="ts"> import TestChildren from './components/testChildren.vue'; const msg = "张三" const list = [1,2,3] </script> <template> <div class="content"> <TestChildren :msg="msg" :list="list"></TestChildren> </div> </template> <style scoped> </style>
t setup 中,引入的组件会自动注册,所以可以直接使用,无需再通过components进行注册
子组件接受值
defineProps 来接收父组件传递的值, defineProps是无须引入的直接使用即可
<template> <div> <p>{{ msg }}</p> <p>{{ list }}</p> <!-- <button @change="handleClick"></button> --> </div> </template> <script setup lang="ts"> // defineProps ts写法 // defineProps<{ // msg:string, // list:number[], // }>() // 两种方法均可 const props = defineProps({ msg:{ type:String }, list:{ type:Array } }) </script> <style> </style>
子组件向父组件抛出事件
defineEmits
子组件派发事件