详细解读Vue3的watchEffect和watch的基础应用
watchEffect
watchEffect
会自动收集函数里面变量的响应式依赖。在初始化的时候watchEffect
会自动执行一次(这是无法阻止的),之后watchEffect
会根据收集到的响应式依赖,在变量发生改变时就会被触发。
接下来看官方的描述:
wactchEffect
:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
- 类型
function watchEffect(
effect: (onCleanup: OnCleanup) => void,
options?: WatchEffectOptions
): StopHandle
type OnCleanup = (cleanupFn: () => void) => void
interface WatchEffectOptions {
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
type StopHandle = () => void
基本使用
下面例子中watchEffect
中只有name
是响应式对像,它会在页面初始化的时候就执行一次用于收集name
的响应式依赖,changeName
事件被触发时,name
被改变了,对应的就会触发watchEffect
;当changeAge
触发时,因为并没有在watchEffect
中使用age
,所以watchEffect
没有收集到对应的响应式依赖,watchEffect
就不会被触发。
<template>
<div id="app">
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改Age</button>
</div>
</template>
<script>
import { watchEffect, defineComponent, ref } from "vue";
export default defineComponent({
setup() {
//watchEffect:自动收集响应式依赖,默认初始化就会执行一次
const name = ref("李四")
const age = ref(18)
watchEffect(() => {
console.log("name:", name.value);
})
const changeName = () => name.value += "1"
const changeAge = () => age.value += 1
return {
name,
age,
changeName,
changeAge
}
},
});
</script>
<style scoped></style>
停止监听
watchEffect
会返回一个函数,这个函数可以用于停止对响应式对象的监听,下面例子中当age > 25
是就会停止监听:
<template>
<div id="app">
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改Age</button>
</div>
</template>
<script>
import { watchEffect, defineComponent, ref } from "vue";
export default defineComponent({
setup() {
//watchEffect:自动收集响应式依赖,默认初始化就会执行一次
const name = ref("李四")
const age = ref(18)
// wacthEffect会返回一个函数,这个函数可用于停止所有的wacthEffect的侦听
const stop = watchEffect(() => {
console.log("userInfo:", name.value,age.value);
})
const changeName = () => name.value += "1"
const changeAge = () => {
age.value += 1
// 当 age > 25 时停止侦听
if(age.value > 25) stop()
}
return {
name,
age,
changeName,
changeAge
}
},
});
</script>
<style scoped></style>
清除副作用
在使用监听的时候我们可能会向服务器发送请求,当监听的数据频繁变化时,这种请求就会频繁触发,这无疑极大的浪费了服务器性能。watchEffect
第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调,下面是官方给的例子:
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(id.value)
// `cancel` 会在 `id` 更改时调用
// 以便取消之前
// 未完成的请求
onCleanup(cancel)
data.value = await response
})
执行时机
有时候我们需要去监听dome
的变化,通过ref
拿到的dome
在watchEffect
第一次执行时是null
,这是因为此时dome
还未渲染完成。watchEffec
的第二个参数是一个可选项,其中flush
可以用来调整watchEffect
执行时机。
下面是官方对flush
的描述:
默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post'
将会使侦听器延迟到组件渲染之后再执行。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync'
来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。
举个栗子:
默认’pre‘:侦听器会在组件渲染前执行,控制台会输出两次,第一次为null
,第二次是页面渲染完成成功获取到组件的时候,会输出组件的引用:
template>
<div id="app">
<h2 ref="name">张三</h2>
</div>
</template>
<script>
import { watchEffect, defineComponent, ref } from "vue";
export default defineComponent({
setup() {
// 执行时机(flush):'pre' | 'post' | 'sync' // 默认'pre'
const name = ref(null)
watchEffect(() => {
console.log("nameDome:", name.value);
})
return {
name
}
},
});
</script>
<style scoped></style>
执行结果截图如下: 我们可以在控制台上看到wathcEffect
在渲染完成之前执行了一次,此时的name
为null
,当渲染完成之后name
的值发生了改变,watchEffect
再次执行,输出这个节点:
修改为flush: 'post'
:它将会使侦听器延迟到组件渲染之后再执行。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。所以控制台只会输出一次,输出的是组件的引用:
<template>
<div id="app">
<h2 ref="name">张三</h2>
</div>
</template>
<script>
import { watchEffect, defineComponent, ref } from "vue";
export default defineComponent({
setup() {
// 执行时机(flush):'pre' | 'post' | 'sync' // 默认'pre'
const name = ref(null)
watchEffect(() => {
console.log("nameDome:", name.value);
}, {
flush: 'post'
})
return {
name
}
},
});
</script>
<style scoped></style>
执行结果截图如下: ,延后执行之后就不会触发一次无意义的监听了
watch
watch
是一个侦听器,默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。先贴官方文档
watch
需要侦听特定的数据源,并在回调函数中执行副作用;- 默认情况下
watch
是懒监听,只有在被监听的数据源发生变化的时候才会执行回调;
watch与watchEffect的区别
watch
默认不会初始化立即执行;watch
有更具体的说明那些状态发生变化是触发侦听器的执行;watch
能够访问侦听状态变化前后的值;
类型参数
下面是官网对watch
类型的描述:
// 侦听单个来源
function watch<T>(
source: WatchSource<T>,
callback: WatchCallback<T>,
options?: WatchOptions
): StopHandle
// 侦听多个来源
function watch<T>(
sources: WatchSource<T>[],
callback: WatchCallback<T[]>,
options?: WatchOptions
): StopHandle
watch
一共有三个参数,分别是:source
、callback
和options
,options
为可选参数。
soure
soure
是一个WatchSource<T>
类型,该类型规定了soure
可以是ref
对象、reactive
对象、数组对象、函数(getter)和普通对象:
type WatchSource<T> =
| Ref<T> // ref
| (() => T) // getter
| T extends object
? T
: never // 响应式对象
callback
watch
的第二个参数callback
是watch
执行的回调,这个函数有三个参数,分别是vaule
(新值)、oldValue
(旧值)、onCleanup
函数(用于清除副作用),下面是官网对于watch
回调函数的描述:
type WatchCallback<T> = (
value: T,
oldValue: T,
onCleanup: (cleanupFn: () => void) => void
) => void
options
options
是可选配置项。我们通过下面接口的描述看到它是继承至WatchEffectOptions
的。immediate
可以控制watch
在组件初始化是是否执行,默认值是false
。
deep
是控制是否开启深度监听的参数,watch
在监听杂的对象时只对表层进行监听,默认值是false
,如果对象的属性还是一个对像,那么这个对象只要地址不改变watch
是不会触发的,通过deep: true
可以监听到深层对象的改变,需要注意的是:1、当watch监听的是一个reactive
对象时会自动开启深度监听;2、如果回调函数是因为深度监听的变更而触发的,那么value
和oldValue
将会是同一个对象。
flush
和watchEffect
一样的flush
,用于控制触发实机,默认pre
,在组件渲染之前执行,下面是官网的描述:
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // 默认:false
deep?: boolean // 默认:false
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
基本使用
我将通过source
传参的不同来举例watch
的基本使用:
- 传入
ref
响应式对象
当监听的是ref
对象时,callback
的value
和oldValue
获得的是ref
对象对应的value
值:
<template>
<div id="app">
<h2>{{ name }}</h2>
<button @click="changeName">改变用户数据</button>
</div>
</template>
<script>
import { watch, defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
// 传入ref对象,newValue和oldValue是对应的value值
const name = ref('张三')
watch(name, (newVlaue,oldValue) => {
console.log('name:',newVlaue,oldValue);
})
const changeName = ()=>{
name.value += "1"
}
return {
name,
changeName
}
},
});
</script>
<style scoped></style>
- 侦听多个来源,
callback
会接收两个数组,对应的顺序是侦听数组的顺序,为了更直观我做了解构,也可以写成(newValue,oldValue)
的形式:
<template>
<div id="app">
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
</h2>
<button @click="changeInfo">改变用户数据</button>
</div>
</template>
<script>
import { watch, defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
// 传入多个对象,newValue和oldValue是对应的value值
const name = ref('张三')
const age = ref(18)
watch([name,age], ([newName,newAge],[oldName,oldAge]) => {
console.log('new:',newName,newAge,'old',oldName,oldAge);
})
const changeInfo = ()=>{
name.value += "1"
age.value += 1
}
return {
name,
age,
changeInfo
}
},
});
</script>
<style scoped></style>
- 传入
reactive
对象,callback
对应的value
和oldValue
都将是reactive
对象,下面userInfo
是一个reactive
对象,所以newValue
和oldValue
都会是reactive
对象:
<template>
<div id="app">
<h2>{{ userInfo.name }}</h2>
<h2>{{ userInfo.age }}</h2>
<button @click="changeInfo">改变用户数据</button>
</div>
</template>
<script>
import { watch, defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
const userInfo = reactive({ name: '张三', age: 18 })
// 传入reactive对象
watch(userInfo, (newValue, oldValue) => {
console.log('userInfo',newValue,oldValue);
})
const changeInfo = ()=>{
userInfo.name += "1"
userInfo.age += 1
}
return {
userInfo,
changeInfo
}
},
});
</script>
<style scoped></style>
如果我们不希望得到响应式的newValue
和oldValue
,那么我们可以使用getter
函数传参方式对reactive
进行解构:
<template>
<div id="app">
<h2>{{ userInfo.name }}</h2>
<h2>{{ userInfo.age }}</h2>
<button @click="changeInfo">改变用户数据</button>
</div>
</template>
<script>
import { watch, defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
const userInfo = reactive({ name: '张三', age: 18 })
// 如果不希望newValue和oldValue是reactive对象可以在传入时对它进行解构
watch(() => { return {...userInfo} }, (newValue, oldValue) => {
console.log('userInfo:',newValue,oldValue);
})
const changeInfo = ()=>{
userInfo.name += "1"
userInfo.age += 1
}
return {
userInfo,
changeInfo
}
},
});
</script>
<style scoped></style>
- 传入getter函数,下面摘自官网的描述:"当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用
{ deep: true }
强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。"
<template>
<div id="app">
<h2>{{ userInfo.name }}</h2>
<h2>{{ userInfo.age }}</h2>
<button @click="changeInfo">改变用户数据</button>
</div>
</template>
<script>
import { watch, defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
const userInfo = reactive({ name: '张三', age: 18 })
// 二、传入getter函数形式
watch(() => userInfo.name, (newVlaue,oldValue) => {
console.log('newValue',newVlaue,'oldValue',oldValue)
})
const changeInfo = ()=>{
userInfo.name += "1"
userInfo.age += 1
}
return {
userInfo,
changeInfo
}
},
});
</script>
<style scoped></style>
以上就是关于Vue3的watchEffect和watch的基础使用了,希望能够帮到你。如果有写得不对的地方,请在评论区留言或者私信我,方便及时改正。
参考文献:
1.vue官方文档
转载自:https://juejin.cn/post/7258886061728350263