D30.Vue
F15.响应式原理 + ref和reactive总结 + setup注意点(K84-K93)
1.Vue3.0中的响应式原理
A.vue2.x的响应式
1)实现原理:
a. 对象类型:通过Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)
b. 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
2)存在问题:
a. 新增属性、删除属性, 界面不会更新
b. 直接通过下标修改数组, 界面不会自动更新
const person = {
name: 'ds',
age: 18,
};
// vue2
let p = {};
Object.defineProperty(p, 'name', {
get() {
console.log('有人读取了name');
return person.name;
},
set(value) {
console.log('有人修改了name');
person.name = value;
},
});
B.Vue3.0的响应式
1)实现原理:
a. 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等
b. 通过Reflect(反射): 对源对象的属性进行操作
c. MDN文档中描述的Proxy与Reflect:
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
//源数据
const person = {
name: 'ds',
age: 18,
};
//代理数据
//target就是目标对象person,propName就是操作的属性
const p = new Proxy(person, {
//有人读取p的某个属性时调用
get(target, propName) {
console.log(`有人读取了p的${propName}`);
console.log(target, propName);
return target[propName];
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target, propName, value) {
console.log(`有人修改了p身上的${propName}`);
target[propName] = value;
},
//有人删除p的某个属性时调用
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${propName}`);
//这个函数需要一个结果来判断成功与否所以return
return delete target[propName];
},
});
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
//源数据
let person = {
name:'张三',
age:18
}
//模拟Vue3中实现响应式
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target,propName)
}
})
通过Reflect操作的属性,报错时会返回false,这样就不要try-catch捕获异常了
2.ref和reactive总结
A.reactive对比ref
1)从定义数据角度对比:
a. ref用来定义:基本类型数据
b. reactive用来定义:对象(或数组)类型数据
c. 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
2)从原理角度对比:
a. ref通过Object.defineProperty()
的get与set来实现响应式(数据劫持)
b. reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据
3)从使用角度对比:
a. ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
b. reactive定义的数据:操作数据与读取数据:均不需要.value
B.ref
1)ref通常用于声明基础类型响应式数据
import {ref} from 'vue'
const age =ref(10) //声明响应式数据
2)ref返回的是被包装过的响应式对象,在setup中访问和修改ref需要使用.value属性
age.value=21
3)在模板中使用时无需使用.value,直接使用即可
<div>{{age}}</div>
4)当ref数据作为props传递给子组件的时候,在子组件里需要使用toRef或者toRefs建立引用,否则数据不是响应式的。且需要注意,如果在子组件中直接操作了这个引用之后,则和父组件不在具有联系
C.reactive
1)reactive用于声明复杂类型响应式数据
import {reactive} from 'vue'
const man=ref({name:'jolin',age:21}) //声明响应式数据
2)reactive返回的是被包装过的响应式对象,在setup中访问和修改直接使用属性即可
man.age=20
3)声明时未定义,动态添加的属性也会是响应式的
man.weight = '50kg' //weight具有响应性
4)在模板中使用属性的形式
<div>{{man.name}}</div>
5)将reactive声明的响应式数据传递给子组件时,在子组件可以直接使用
6)当ref的值是数组时,我们可以通过索引来修改数组值是响应式的
D.注意事项
1)注意当ref用于在模板中作为真值判断时,直接使用ref恒为true, 需要使用.value才能正确显示
<div v-if="age"></div> //恒为true
<div v-if="age.value"></div> //正确
2)注意不能解构reactive数据,解构出的数据会失去响应式
3)在任何地方访问响应式数据都能拿到最新的
4)同vue2的data,只有数据被应用到模板中时,数据的改变才会触发updated生命周期,否则即使数据被修改了,也不会触发updated生命周期,导致视图不更新
5)同vue2,当将ref和reactive作为props传递给组件时,原则上不应该在子组件上修改props的值
3.setup的两个注意点
1)setup执行的时机
a. 在beforeCreate之前执行一次,this是undefined
2)setup的参数
a. props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
b. context:上下文对象
attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
。 如果子组件没有使用props:[‘xxx’]
接收,attr这个对象就能看到父组件传来的数据
slots: 收到的插槽内容, 相当于 this.$slots
emit: 分发自定义事件的函数, 相当于 this.$emit
。 一定要在配置里写emits:['hello']
, 这里要写父组件的自定义函数名称,不然会有警告
App.vue
<template>
<Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷">
<template v-slot:qwe>
<span>尚硅谷</span>
</template>
<template v-slot:asd>
<span>尚硅谷</span>
</template>
</Demo>
</template>
<script>
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
setup(){
function showHelloMsg(value){
alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`)
}
return {
showHelloMsg
}
}
}
</script>
Demo.vue
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<button @click="test">测试触发一下Demo组件的Hello事件</button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'Demo',
props:['msg','school'],
emits:['hello'], //这里要写父组件的自定义函数名称,不然会有警告
setup(props,context){
// console.log('---setup---',props)传过来的属性存在这个对象里面
// console.log('---setup---',context)
// console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
// console.log('---setup---',context.emit) //触发自定义事件的。
console.log('---setup---',context.slots) //插槽
//数据
let person = reactive({
name:'张三',
age:18
})
//方法
function test(){
context.emit('hello',666)
}
//返回一个对象(常用)
return {
person,
test
}
}
}
</script>
F16.计算属性 + 侦听器(K94-K103)
1.计算属性
有时我们需要依赖于其他状态(普通proxy响应式数据)的状态(computed响应式数据): 在 Vue 中,这是用组件计算属性处理的,以直接创建计算值,我们可以使用 computed 函数:它接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 ref 对象
A.computed函数
与Vue2.x中computed配置功能一致
可以直接去看3.7.3完整写法
1)模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象:
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
2)我们想根据 author 是否已有一些书籍来展示不同的信息:
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于 author.books。更重要的是,如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍
3)因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。这是重构后的示例:
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
B.其它使用
1)接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象
2)该案例功能常用于父组件双向绑定,在子组件 props与computed结合使用很方便!
import { ref,computed } from 'vue';
let test2_count = ref(0)
let test2 = computed({
// test2.value = 3 则会触发 set()
// set/get里都不能操作test2.value,否则会报错
get: () => {
return test2_count.value + '可变的响应式ref对象'
},
set: (val:any) => {
test2_count.value = val - 11
}
})
test2.value = 1 // test2_count.value = 1 - 11
3)调试 Computed
computed 可接受一个带有 onTrack 和 onTrigger 选项的对象作为第二个参数:
a. onTrack 会在某个响应式 property 或 ref 作为依赖被追踪时调用
b. onTrigger 会在侦听回调被某个依赖的修改触发时调用
所有回调都会收到一个 debugger 事件参数,其中包含了一些依赖相关的信息。推荐在这些回调内放置一个 debugger 语句以调试依赖
import { ref,computed } from 'vue';
let count = ref(147)
let countComputed = computed(() => count.value + '计算属性',{
onTrack(e) {
// 当 count.value 作为依赖被追踪时触发。用人话将就是被访问的时候触发
console.log('onTrack',e);
},
onTrigger(e) {
// 当 count.value 被修改时触发
console.log('onTrigger',e);
}
})
注意:onTrack 和 onTrigger 仅在开发模式下生效 !!
C.计算属性vs方法
1)可能注意到我们在表达式中像这样调用一个函数也会获得和计算属性相同的结果:
<p>{{ calculateBooksMessage() }}</p>
// 组件中
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数
2)这也解释了为什么下面的计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖:
const now = computed(() => Date.now())
3)const now = computed(() => Date.now())
为什么需要缓存呢?想象一下我们有一个非常耗性能的计算属性 list,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list。没有缓存的话,我们会重复执行非常多次 list 的计算函数,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用
4)官网的建议
a. 计算函数不应有副作用# 计算属性的计算函数应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。举例来说,不要在计算函数中做异步请求或者更改 DOM!一个计算属性的声明中描述的是如何根据其他值派生一个值。因此计算函数的职责应该仅为计算和返回该值。在之后的指引中我们会讨论如何使用监听器根据其他响应式状态的变更来创建副作用
b. 避免直接修改计算属性值# 从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算
2.侦听器
A.基本使用
1)在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数
与Vue2.x中watch配置功能一致
2)详细信息
watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数
第一个参数是侦听器的源。这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个 ref
- ...或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值
第三个可选的参数是一个对象,支持以下这些选项:- immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
- deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器一节
- flush:调整回调函数的刷新时机。参考回调的刷新时机一节
- onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器一节
缓冲回调:缓冲回调不仅可以提高性能,还有助于保证数据的一致性。在执行数据更新的代码完成之前,侦听器不会被触发。简单来说,同步修改数据时,修完操作执行完毕后才会触发回调。注意是同步!所以异步操作的时候,要注意多次触发watch的问题。(所有同步操作为1次,异步操作有几次就触发多少次监听!!)
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
a. 功能1:停止侦听
此处监听的ref对象,传入的是ref对象则会自动解包 .value(基于源码的解释)<script lang='ts' setup> import { watch,ref } from 'vue'; let count = ref(0) //! 关于 watch() 返回的 StopHandle 函数,调用stop()将会停止侦听。 let stop = watch(count, (newValue, oldValue,InvalidateCbRegistrator) => { console.log('watch : count-:' + newValue, oldValue) }) setTimeout(() => stop(), 3333); // stop() 停止侦听 </script>
b. 监听 reactive() 的对象:
直接监听reactive声明的proxy对象,最终vue会默认赋值为true,所以自己传什么都没用。(基于源码的解释)<script lang='ts' setup> import { watch,reactive } from 'vue'; let proxy1 = reactive({}) watch(proxy1, (newValue, oldValue) => { console.log('proxy1--',newValue , oldValue ) },{ deep:'干啥勒,对我做啥我都不从' //! 基于上述解释,此处传什么都是无效的,最终会被默认覆盖为true }) </script>
c. 监听嵌套对象的某个属性 (传入函数)
单独监听嵌套的某个属性 则需要传入函数的返回值<script lang='ts' setup> import { watch,reactive } from 'vue'; // reactive ref都行,看了 refs 篇章你也知道其原理 let proxy1 = reactive({t1:'嵌套数据'}) watch(()=>proxy1.t1, (newValue, oldValue) => { console.log(newValue, oldValue) }) </script>
d. 监听多个源的形式 (传入数组)
<script lang='ts' setup> import { watch,reactive } from 'vue'; let data1 = reactive({t1:'t1嵌套数据'}) let data2 = reactive({t2:'t2嵌套数据'}) watch([data1,data2], (newValue, oldValue) => { console.log(newValue, oldValue) }) </script>
注意,你不能直接侦听响应式对象的属性值,例如:
const obj = reactive({ count: 0 }) // 错误,因为 watch() 得到的参数是一个 number watch(obj.count, (count) => { console.log(`count is: ${count}`) })
这里需要用一个返回该属性的 getter 函数:
// 提供一个 getter 函数 watch( () => obj.count, (count) => { console.log(`count is: ${count}`) } )
e. deep的使用:
直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:const obj = reactive({ count: 0 }) watch(obj, (newValue, oldValue) => { // 在嵌套的属性变更时触发 // 注意:`newValue` 此处和 `oldValue` 是相等的 // 因为它们是同一个对象! }) obj.count++
相比之下,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调:
watch( () => state.someObject, () => { // 仅当 state.someObject 被替换时触发 } )
你也可以给上面这个例子显式地加上 deep 选项,强制转成深层侦听器:
watch( () => state.someObject, (newValue, oldValue) => { // 注意:`newValue` 此处和 `oldValue` 是相等的 // *除非* state.someObject 被整个替换了 }, { deep: true }
谨慎使用
深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能
f. 第三个参数的3种形式:
(1pre 模式下,指定的回调在模板渲染前被调用,所以立马获取对应的内容,将不会是最新的!!
(2pre 模式下,指定的回调在模板渲染前被调用,所以立马获取对应的内容,将不会是最新的!!
(3sync 就不多说了,回调改为同步调用,即取消 <缓冲回调> 这个功能
(4’pre’ 和 ‘post’ 回调使用队列进行缓冲,所以<同步>多次修改后,只触发最后一次监听回调<script lang='ts' setup> import { watch,reactive } from 'vue'; let data1 = reactive({t1:'t1嵌套数据'}) watch(data1, (newValue, oldValue) => { console.log('data1--',newValue, oldValue) },{ deep:false, immediate:false, flush:'post' // 'pre' | 'post' | 'sync' // 默认值是'pre' }) </script>
g. callback的第三个参数 onInvalidate > 注册失效回调 关于这个的解释,请移步watchEffect()文章,再来看这第六点,这样理解起来最佳!
<script lang='ts' setup> import { watch,reactive } from 'vue'; let data1 = reactive({t1:'t1嵌套数据'}) let stop = watch(data1, (newValue, oldValue,onInvalidate ) => { onInvalidate(()=>{ console.log('执行了'); }) }) data1.t1 = 1 settimeout(() => data1.t1 = 321, 1500) setTimeout(() => stop(), 3333); // stop() 停止侦听 </script>
该注册回调的触发机制:
副作用即将重新执行时(第一次不执行)
侦听器被停止 (若在 setup() 或生命周期钩子函数中使用了watchEffect,在组件卸载时会自动停止侦听。)
与 watchEffect 共享的行为:
watch 与 watchEffect共享停止侦听,清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机和侦听器调试行为
h. 组件创建时的生命周期里同步执行的侦听会被收集,组件销毁时会自动销毁侦听器。 组件创建完后的再创建的侦听器,需要自己手动销毁
B.watch 函数总结
1)两个小“坑”:
a. 监视reactive定义的响应式数据时:oldValue无法正确获取、默认强制开启了深度监视(deep配置失效)
b. 监视reactive定义的响应式数据中某个属性时:deep配置有效//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
如果能使用computed实现的就不用watch
C.watchEffect函数
1)为了根据响应式状态自动应用 和 重新应用 副作用,我们可以使用 watchEffect 函数。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。 传入的函数就是一个 effect函数 ,它会根据侦听的响应式数据的变化执行该函数
a. watch的套路是:既要指明监视的属性,也要指明监视的回调
b. watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性,并且初始就会执行一次回调函数
c. watchEffect有点像computed:
但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
d. 立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数
注意,响应式数据必须要触发get才能劫持对应的内容为该副作用函数的依赖
e. watchEffect 接收2个参数 ,并且有返回一个 StopHandle 函数用来停止侦听
第一个参数:(必传) effect 函数,收集依赖,并且组件初始化时立即执行一次; 并且 effect 函数可以接受一个 onInvalidate 函数参数,该参数执行并传入一个 callback ,每次监听回调执行前都会执行该 callback
第二个参数对象(非必传): flush 、 onTrack 、 onTrigger; flush 跟 watch() $watch() watch:{} 的flush 完全一致。同理也具备 <缓冲回调> onTrack 、 onTrigger 看代码写的注释讲解!2)提示
watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪watchEffect(async () => { const response = await fetch(url.value) data.value = await response.json() })
这个例子中,回调会立即执行。在执行期间,它会自动追踪 url.value 作为依赖(和计算属性的行为类似)。每当 url.value 变化时,回调会再次执行
关于这个例子,我觉得fetch函数应该比await先执行,这样才能满足在第一个 await 正常工作前访问到的属性才会被追踪这句话
D.回调的触发时机
1)当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调
2)默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态
3)副作用刷新时机 flush 一般使用post
flush:'pre' // 'pre' | 'post' | 'sync' // 默认值是'pre' // pre 模式下,指定的回调在模板渲染前被调用,所以立马获取对应的内容,将不会是最新的!! // post 模式下,将回调推迟到模板渲染之后的,这时候用$ref获取对应内容则会是最新的,等于 pre 模式下用 nextTice() // sync 就不多说了,回调改为同步调用,即取消 <缓冲回调> 这个功能 // 'pre' 和 'post' 回调使用队列进行缓冲,这也是为什么<同步>多次修改后,只触发最后一次监听回调的原因。
4)如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: ‘post’ 选项:
watch(source, callback, { flush: 'post' }) watchEffect(callback, { flush: 'post' })
5)后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect():
import { watchPostEffect } from 'vue' watchPostEffect(() => { /* 在 Vue 更新后执行 */ })
a. watchPostEffect v3.2+
b. watchEffect 的别名,固定 flush: ‘post’ 选项
c. watchSyncEffect v3.2+
d. watchEffect 的别名,固定 flush: ‘sync’ 选项
e. watchSyncEffect 为例,简单讲就是除了 flush 参数固定为 ‘sync’ ,其他所有功能跟 watchEffect 一样
5)onTrigger 可以帮助我们调试 watchEffectimport { watchEffect, ref } from 'vue' let message = ref<string>('') let message2 = ref<string>('') watchEffect((oninvalidate) => { //console.log('message', message.value); oninvalidate(()=>{ }) console.log('message2', message2.value); },{ flush:"post", //! 跟他名字一样依赖更改就触发执行,而且是同步的!没有所谓的缓冲回调 onTrigger(e){console.log(e.target,'onTrigger触发')} })
E.停止侦听
// 返回值是一个用来停止该副作用的函数。 const stop = watchEffect(() => { console.log(obj.data); }) setTimeout(() => { stop() // 停止侦听 则调用该返回值即可 }, 1000*5);
F.清除副作用
1)在上面的代码中,你细心的话会注意到副作用函数有 onInvalidate 这么个函数参数!
有时副作用函数内会执行一些异步的函数,这些异步响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
a. 副作用即将重新执行时
b. 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,在组件卸载时会自动停止侦听。)
我们之所以是通过传入一个函数去注册某个功能的失效回调,而不是从回调返回它,是因为返回值对于异步错误处理很重要
在执行数据请求时,副作用函数往往是一个异步函数: 当id的值变化快,频繁http请求时,若前面的请求未完成,则会造成重复请求,则可以使用副作用函数的onCleanup参数,取消之前的http请求。 (onCleanup是vue3最新版英文官网的名字定义