在讲 watch 之前,我们先来看看 watchEffect
一、 watchEffect
立即执行传入的一个函数,同时==响应式追踪其依赖==,并在其依赖变更时重新运行该函数。
watchEffect
的一些特点:
不需要手动传入依赖(不用指定监听对象)
无法获取原始值,只能获取更新后的值
立即执行(在onMounted
前调用)
一些异步操作放里面更加的合适
watchEffect
第一个参数是一个箭头函数(是一个副作用函数),里面用来获取侦听到的新值以及用来进行其它操作(比如清除副作用等)
const count = ref(0)
watchEffect(() =>{
console.log(count.value)
// -> logs 0
})
setTimeout(() => {
count.value++
// -> logs 1
}, 1000)
复制代码
1. 停止监听
当 watchEffect
在组件的 setup()
函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
但在某些场景下我们需要手动的去停止监听,则我们可使用如下写法:
const stop = watchEffect(() => {
/* ... */
})
// 停止监听
stop()
复制代码
2. 更改监听时机(更改副作用刷新时机)
先来看看副作用的刷新时机也就是监听时机:
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
复制代码
在这个例子中:
count
会在初始运行时同步打印出来
更改 count
时,将在组件更新前执行副作用。
更改watchEffect
的监听时机可以对其设置flush
的值
flush
取值:
例如:
const state = reactive({
id: 1,
attributes: {
name: '',
}
})
复制代码
watchEffect(() => {
console.log('名字被修改了', state.attributes.name);
})
state.attributes.name = 'Ailjx'
/**
* 打印结果(因为在组件更新前侦听就执行了一次,所以会打印两次)
* 名字被修改了
* 名字被修改了 Ailjx
*/
复制代码
watchEffect(() => {
console.log('名字被修改了', state.attributes.name);
}, {
flush: 'post'
})
state.attributes.name = 'Ailjx'
/**
* 打印结果(组件更新后触发,所以只会打印一次)
* 名字被修改了 Ailjx
*/
复制代码
3. 清除副作用
在说清除副作用前先来了解一下什么是副作用:副作用是指一个函数在返回值时还干了其它事(该函数称之为副作用函数)如:
修改一个变量
设置一个对象的成员
终端打印日志
修改 DOM
时间监听或订阅
等等
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。watchEffect
可在第一个参数(函数)里传入一个参数onInvalidate
用来清除副作用
onInvalidate
执行时间:
const id = ref(0);
watchEffect((onInvalidate) => {
//假设asyncOperation函数为一个异步请求操作,是副作用函数,这里只是用来演示,不用纠结这个函数具体是干嘛的
const token = asyncOperation(id.value);
onInvalidate(() => {
/**
* 如果id已更改,或watchEffect停止运行时执行:
* token.cancel();
* cancel()的作用是取消请求,这里用与取消token的异步请求来取消异步请求操作产生的副作用
*/
token.cancel();
});
});
复制代码
4. 侦听器调试
onTrack
和 onTrigger
选项可用于调试侦听器的行为。
这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。建议在以下回调中编写 debugger 语句来检查依赖关系:
watchEffect(
() => {
/* 副作用 */
},
{
onTrigger(e) {
debugger
}
}
)
复制代码
onTrack
和 onTrigger
只能在开发模式下工作。
一、watch
1. 监听单一源
// 侦听 reactive
const state = reactive({ count: 0 })
watch(() => state.count,
(count, prevCount) => {
/* count为新值,prevCount为旧值 */
}
)
// 直接侦听ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
复制代码
2. 监听多个源
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
firstName.value = 'John' // logs: ["John", ""] ["", ""]
lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""]
复制代码
如果你在同一个函数里同时改变这些被侦听的来源,并且该函数不是声明后不立即调用,则侦听器仍只会执行一次:==多个同步更改只会触发一次侦听器。==
<button @click="changeValues">改变</button>
复制代码
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
const changeValues = () => {
firstName.value = 'John'
lastName.value = 'Smith'
// 只打印一个 ["John", "Smith"] ["", ""] (说明监听只触发了一次)
}
复制代码
但如果你在同一个函数里同时改变这些被侦听的来源,并且这个函数在声明后就被调用,则侦听器会执行多次:
例如上述代码改为:
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
const changeValues = () => {
firstName.value = 'John'
lastName.value = 'Smith'
//打印两次,则侦听器执行了两次
// ["John", ""] ["", ""]
// ["John", "Smith"] ["John", ""]
}
//在声明后立即调用
changeValues()
复制代码
如果你想要让侦听器始终能够监听多次(为每个更改都强制触发侦听器),可以设置flush: 'sync'
(不推荐):
<button @click="changeValues">改变</button>
复制代码
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log('改变的-->', newValues, prevValues)
}, {
flush: 'sync'
})
const changeValues = () => {
firstName.value = 'John'
lastName.value = 'Smith'
//打印两次,则侦听器执行了两次
// ["John", ""] ["", ""]
// ["John", "Smith"] ["John", ""]
}
复制代码
或者,可以用 nextTick 等待侦听器在下一步改变之前运行。例如:
const changeValues = async () => {
firstName.value = 'John' // 打印 ["John", ""] ["", ""]
await nextTick()
lastName.value = 'Smith' // 打印 ["John", "Smith"] ["John", ""]
}
复制代码
3. 侦听响应式对象
使用侦听器来比较一个数组或对象的值,这些值是响应式的,要求它有一个由值构成的副本。
const numbers = reactive([1, 2, 3, 4])
//...numbers解构numbers
watch(() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers)
}
)
numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
复制代码
4. 深度监听
当监听源为多重嵌套时,想要监听到该源的深层部分则需要设置deep: true
例如在监听state
时需要同时监听它里的attributes.name
:
const state = reactive({
id: 1,
attributes: {
name: '',
}
})
复制代码
watch(
() => state,
(state, prevState) => {
console.log('deep', state.attributes.name, prevState.attributes.name)
},
{ deep: true }
)
//根据打印结果发现打印出的新旧值都为改变后的值
state.attributes.name = 'Alex' // 日志: "deep" "Alex" "Alex"
复制代码
但这样使用后会发现watch
侦听打印的新旧值都是修改后的值,为了完全侦听深度嵌套的对象和数组,可能需要对值进行==深拷贝==:
// 使用JSON.parse(JSON.stringify(state))对state进行深拷贝
watch(
() => JSON.parse(JSON.stringify(state)),
(state, prevState) => {
console.log('deep', state.attributes.name, prevState.attributes.name)
},
{ deep: true }
)
state.attributes.name = 'Alex' // 日志: "deep" "Alex" ""
复制代码
5. 立即执行
如果想要 watch 函数立即执行,这可设置immediate:true
watch(
() => JSON.parse(JSON.stringify(state)),
(state, prevState) => {
console.log('deep', '新值:' + state.attributes.name, '旧值:' + prevState.attributes.name)
},
{
//立即执行
immediate: true,
deep: true
},
)
复制代码
watch
与 watchEffect
共享停止侦听,清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机和侦听器调试行为。例如:
const stop = watch(
() => JSON.parse(JSON.stringify(state)),
(state, prevState, onInvalidate) => {
console.log('deep', '新值:' + state.attributes.name, '旧值:' + prevState.attributes.name)
onInvalidate(() => {
/* 清除副作用代码 */
})
},
{
immediate: true,
deep: true,
//侦听器调试
onTrigger(e) {
debugger
}
},
)
stop()
复制代码
评论