watch 详解
watch() 用于监听响应式数据的变化并执行回调。Vitarx 提供了多种 watch 变体,支持监听不同类型的数据源,以及 watchEffect 系列函数用于自动追踪依赖。
watch() 四种重载
1. 监听 Ref
直接监听一个 ref(ValueRef 或 ShallowRef),当 .value 变化时触发回调:
import { ref, watch } from 'vitarx'
const count = ref(0)
const watcher = watch(count, (newValue, oldValue) => {
console.log(`count: #123;oldValue} → #123;newValue}`)
})
count.value = 1 // 输出: "count: 0 → 1"2. 监听响应式对象
监听一个 reactive 对象,当对象内部属性变化时触发回调。需要配合 deep 选项使用:
import { reactive, watch } from 'vitarx'
const state = reactive({ name: '张三', profile: { age: 25 } })
watch(
state,
(newValue, oldValue) => {
console.log('state 变化了')
},
{ deep: true }
)
state.name = '李四' // 触发回调
state.profile.age = 26 // 触发回调(深度监听)WARNING
监听 reactive 对象时,回调中的 newValue 和 oldValue 指向同一个对象(因为对象是引用类型,修改的是同一个代理对象)。如果需要获取旧值快照,请使用 getter 函数方式。
3. 监听 getter 函数
通过一个 getter 函数指定要监听的值,getter 内访问的响应式数据都会被追踪:
import { reactive, watch } from 'vitarx'
const state = reactive({ name: '张三', age: 25 })
// 只监听 name 属性的变化
watch(
() => state.name,
(newValue, oldValue) => {
console.log(`name: #123;oldValue} → #123;newValue}`)
}
)
state.name = '李四' // 触发回调
state.age = 30 // 不触发——getter 中没有访问 age4. 监听多数据源
传入一个数组,同时监听多个数据源。任意一个变化都会触发回调,回调参数也是数组:
import { ref, watch } from 'vitarx'
const firstName = ref('张')
const lastName = ref('三')
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`#123;oldFirst}#123;oldLast} → #123;newFirst}#123;newLast}`)
})
firstName.value = '李' // 输出: "张三 → 李三"数组中可以混合使用 ref、reactive 对象和 getter 函数:
import { ref, reactive, watch } from 'vitarx'
const count = ref(0)
const state = reactive({ name: '张三' })
watch([count, () => state.name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count: #123;oldCount}→#123;newCount}, name: #123;oldName}→#123;newName}`)
})WatchOptions
watch() 的第三个参数是配置对象,支持以下选项:
immediate
是否在创建时立即执行一次回调。默认为 false。
import { ref, watch } from 'vitarx'
const count = ref(0)
watch(
count,
(newValue, oldValue) => {
console.log(`count: #123;oldValue} → #123;newValue}`)
},
{ immediate: true }
)
// 立即输出: "count: undefined → 0"INFO
使用 immediate: true 时,第一次回调的 oldValue 是 undefined。
deep
是否深度监听。当监听 reactive 对象时,需要设置 deep: true 才能追踪嵌套属性的变化。默认为 false。
import { reactive, watch } from 'vitarx'
const state = reactive({ profile: { age: 25 } })
// 不设置 deep——只监听根级属性
watch(state, () => {
console.log('state 变化了')
})
state.profile.age = 26 // 不触发
// 设置 deep——深度监听
watch(
state,
() => {
console.log('state 深度变化了')
},
{ deep: true }
)
state.profile.age = 27 // 触发deep 也可以传入数字,指定遍历深度:
watch(state, callback, { deep: 2 }) // 只遍历 2 层深度once
是否只监听一次。回调执行一次后自动停止监听。默认为 false。
import { ref, watch } from 'vitarx'
const count = ref(0)
watch(
count,
(newValue, oldValue) => {
console.log(`count: #123;oldValue} → #123;newValue}`)
},
{ once: true }
)
count.value = 1 // 输出: "count: 0 → 1"
count.value = 2 // 不触发——已经停止监听flush
指定回调的执行时机。默认为 'pre'。
| 值 | 说明 |
|---|---|
'pre' | 在视图更新前执行(默认) |
'post' | 在视图更新后执行 |
'sync' | 同步执行——数据变化后立即执行回调 |
import { ref, watch } from 'vitarx'
const count = ref(0)
// DOM 更新后执行
watch(
count,
(newValue) => {
// 此时 DOM 已经更新完毕
console.log('count 变化了:', newValue)
},
{ flush: 'post' }
)
// 同步执行——数据变化后立即执行
watch(
count,
(newValue) => {
console.log('同步回调:', newValue)
},
{ flush: 'sync' }
)WARNING
flush: 'sync' 会同步执行回调,如果回调中有耗时操作,可能会影响性能。flush: 'main' 是框架内部使用的模式,业务代码不应使用。
watchEffect()
watchEffect() 创建自动追踪依赖的副作用。与 watch 不同,它不需要指定监听源——函数内访问的响应式数据都会被自动追踪。
import { ref, watchEffect } from 'vitarx'
const count = ref(0)
const name = ref('张三')
// 自动追踪 count 和 name
watchEffect(() => {
console.log(`count=#123;count.value}, name=#123;name.value}`)
})
// 立即输出: "count=0, name=张三"
count.value = 1 // 输出: "count=1, name=张三"
name.value = '李四' // 输出: "count=1, name=李四"watchEffect 与 watch 的区别
| 特性 | watch | watchEffect |
|---|---|---|
| 监听源 | 需要显式指定 | 自动追踪 |
| 是否立即执行 | 默认否(需 immediate: true) | 是 |
| 获取旧值 | 可以 | 不可以 |
| 适用场景 | 需要精确控制监听目标 | 依赖关系简单,不需要旧值 |
watchPostEffect()
watchPostEffect() 是 watchEffect 的 flush: 'post' 版本——副作用在视图更新后执行:
import { ref, watchPostEffect } from 'vitarx'
const count = ref(0)
watchPostEffect(() => {
// DOM 更新后执行
console.log('count 变化了:', count.value)
})等价于:
watchEffect(
() => {
console.log('count 变化了:', count.value)
},
{ flush: 'post' }
)watchSyncEffect()
watchSyncEffect() 是 watchEffect 的 flush: 'sync' 版本——数据变化后同步执行副作用:
import { ref, watchSyncEffect } from 'vitarx'
const count = ref(0)
watchSyncEffect(() => {
// 同步执行——数据变化后立即执行
console.log('count 变化了:', count.value)
})onCleanup 清理副作用
watch 和 watchEffect 的回调都支持注册清理函数——在下一次回调执行前或监听器被停止时调用。用于清理上一次副作用产生的资源(如定时器、网络请求等)。
watch 中的 onCleanup
import { ref, watch } from 'vitarx'
const id = ref(1)
watch(id, (newValue, oldValue, onCleanup) => {
const timer = setTimeout(() => {
console.log(`请求 id=#123;newValue} 的数据`)
}, 1000)
// 注册清理函数——下次回调执行前调用
onCleanup(() => {
clearTimeout(timer)
console.log(`清理 id=#123;oldValue} 的请求`)
})
})
id.value = 2 // 先清理 id=1 的定时器,再设置 id=2 的定时器watchEffect 中的 onCleanup
import { ref, watchEffect } from 'vitarx'
const keyword = ref('')
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log(`搜索: #123;keyword.value}`)
}, 300)
onCleanup(() => {
clearTimeout(timer)
})
})Watcher 的 dispose / pause / resume
watch() 和 watchEffect() 返回的都是 Watcher 实例,支持手动控制生命周期:
dispose() — 停止监听
import { ref, watch } from 'vitarx'
const count = ref(0)
const watcher = watch(count, (newValue) => {
console.log('count:', newValue)
})
count.value = 1 // 输出: "count: 1"
// 停止监听
watcher.dispose()
count.value = 2 // 不再触发回调pause() 和 resume() — 暂停和恢复
import { ref, watch } from 'vitarx'
const count = ref(0)
const watcher = watch(count, (newValue) => {
console.log('count:', newValue)
})
// 暂停监听
watcher.pause()
count.value = 1 // 不触发回调
// 恢复监听
watcher.resume()
count.value = 2 // 触发回调完整示例
下面是一个综合使用 watch 相关 API 的示例:
import { ref, reactive, computed, watch, watchEffect, createApp } from 'vitarx'
function App() {
const keyword = ref('')
const results = ref<string[]>([])
const searchCount = ref(0)
// 1. watch getter:监听搜索关键词
watch(
() => keyword.value,
(newVal, oldVal, onCleanup) => {
console.log(`搜索: "#123;oldVal}" → "#123;newVal}"`)
// 模拟异步搜索
const timer = setTimeout(() => {
results.value = newVal ? [`结果1-#123;newVal}`, `结果2-#123;newVal}`] : []
searchCount.value++
}, 300)
// 清理上一次的定时器
onCleanup(() => clearTimeout(timer))
}
)
// 2. watchEffect:自动追踪依赖
watchEffect(() => {
document.title = `搜索: #123;keyword.value || '首页'}`
})
// 3. watch 多数据源
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed(() => `#123;firstName.value}#123;lastName.value}`)
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(`姓名变为: #123;newFirst}#123;newLast}`)
})
return (
<div style={{ padding: '20px' }}>
<h2>watch 详解示例</h2>
<div style={{ marginTop: '16px' }}>
<h3>搜索功能(watch + onCleanup)</h3>
<input
value={keyword.value}
onInput={(e) => (keyword.value = e.target.value)}
placeholder="输入关键词搜索"
/>
<p>搜索次数: {searchCount}</p>
<ul>
{results.value.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
<div style={{ marginTop: '16px' }}>
<h3>多数据源监听</h3>
<p>姓名: {fullName}</p>
<input
value={firstName.value}
onInput={(e) => (firstName.value = e.target.value)}
placeholder="姓"
/>
<input
value={lastName.value}
onInput={(e) => (lastName.value = e.target.value)}
placeholder="名"
/>
</div>
</div>
)
}
createApp(App).mount('#app')下一步
- 副作用与作用域 — 深入理解 EffectScope 和副作用生命周期管理