监听器
监听器用于在响应式数据变化时执行副作用,比如发送请求、操作 DOM、打印日志等。本页介绍 watch 和 watchEffect 的用法。
watch
watch 用于监听特定的响应式数据源,在数据变化时执行回调。
监听 ref
tsx
import { ref, watch, createApp } from 'vitarx'
function App() {
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`count 从 #123;oldVal} 变为 #123;newVal}`)
})
return (
<div>
<p>{count}</p>
<button onClick={() => count.value++}>+1</button>
</div>
)
}
createApp(App).mount('#app')监听 getter 函数
当你只想监听对象的某个属性时,使用 getter 函数:
tsx
import { reactive, watch } from 'vitarx'
const state = reactive({ name: '张三', age: 25 })
// 只监听 name 的变化
watch(
() => state.name,
(newVal, oldVal) => {
console.log(`name 从 #123;oldVal} 变为 #123;newVal}`)
}
)监听多个数据源
传入数组可以同时监听多个数据源:
tsx
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}`)
})监听 reactive 对象
直接监听 reactive 对象时,需要配合 deep 选项:
tsx
import { reactive, watch } from 'vitarx'
const state = reactive({ user: { name: '张三' } })
watch(
state,
(newVal) => {
console.log('state 发生了变化')
},
{ deep: true }
)watchEffect
watchEffect 会自动追踪内部使用的响应式数据,当这些数据变化时自动重新执行:
tsx
import { ref, watchEffect, createApp } from 'vitarx'
function App() {
const count = ref(0)
// 自动追踪 count,count 变化时自动重新执行
watchEffect(() => {
console.log(`当前计数: #123;count.value}`)
})
return (
<div>
<p>{count}</p>
<button onClick={() => count.value++}>+1</button>
</div>
)
}
createApp(App).mount('#app')与 watch 的区别
| 特性 | watch | watchEffect |
|---|---|---|
| 指定监听源 | 需要显式指定 | 自动追踪 |
| 获取旧值 | ✅ 回调提供新旧值 | ❌ 无法获取 |
| 懒执行 | 默认不执行,数据变化才执行 | 创建时立即执行一次 |
| 适用场景 | 需要知道变化前后的值 | 只关心最新值,执行副作用 |
onCleanup 清理副作用
在回调中注册清理函数,避免内存泄漏。清理函数会在下次回调执行前或监听器停止时调用:
tsx
import { ref, watch } from 'vitarx'
const id = ref(1)
watch(id, (newId, oldId, onCleanup) => {
const timer = setTimeout(() => {
console.log(`加载用户 #123;newId} 的数据`)
}, 1000)
// 注册清理函数——下次 id 变化时,清除上一次的定时器
onCleanup(() => {
clearTimeout(timer)
console.log(`清理用户 #123;oldId} 的请求`)
})
})watchEffect 同样支持 onCleanup:
tsx
watchEffect((onCleanup) => {
const timer = setInterval(() => {
console.log('定时检查:', count.value)
}, 1000)
onCleanup(() => {
clearInterval(timer)
})
})immediate 选项
watch 默认是懒执行的——只在数据变化时才触发回调。设置 immediate: true 可以在创建时立即执行一次:
tsx
const count = ref(0)
watch(
count,
(newVal, oldVal) => {
console.log(`count: #123;newVal}`)
},
{ immediate: true }
)
// 立即输出: "count: 0"deep 选项
当需要深度监听对象内部嵌套属性的变化时,设置 deep: true:
tsx
import { reactive, watch } from 'vitarx'
const state = reactive({
user: {
profile: {
age: 25
}
}
})
// 深度监听——任何嵌套属性变化都会触发
watch(
state,
(newVal) => {
console.log('state 发生了变化')
},
{ deep: true }
)
// 修改深层属性也会触发回调
state.user.profile.age = 26WARNING
深度监听会遍历对象的所有嵌套属性,对大型对象可能产生性能开销。请谨慎使用,只在确实需要时开启。
flush 选项
flush 控制回调的执行时机:
| 值 | 说明 |
|---|---|
'pre' | DOM 更新前执行(默认) |
'post' | DOM 更新后执行 |
'sync' | 同步执行,数据变化后立即执行 |
tsx
watch(
count,
(newVal) => {
// 在 DOM 更新后执行,可以访问更新后的 DOM
console.log('DOM 已更新')
},
{ flush: 'post' }
)INFO
大多数情况下使用默认的 'pre' 即可。当你需要在回调中访问更新后的 DOM 时,使用 'post'。'sync' 模式下数据变化会立即同步执行回调,谨慎使用以避免性能问题。
watch vs watchEffect 选择建议
- 需要新旧值对比 → 用
watch - 需要精确控制监听什么 → 用
watch - 只关心最新值,执行副作用 → 用
watchEffect - 不确定用哪个 → 用
watch,它更明确可控
完整示例
下面是一个搜索防抖的完整示例:
tsx
import { ref, watch, createApp } from 'vitarx'
function App() {
const keyword = ref('')
const results = ref<string[]>([])
const loading = ref(false)
// 监听搜索关键词,模拟异步搜索
watch(keyword, (newVal, oldVal, onCleanup) => {
if (!newVal.trim()) {
results.value = []
return
}
loading.value = true
const timer = setTimeout(() => {
// 模拟搜索结果
results.value = [`#123;newVal} - 结果1`, `#123;newVal} - 结果2`, `#123;newVal} - 结果3`]
loading.value = false
}, 500)
// 清理上一次的请求
onCleanup(() => {
clearTimeout(timer)
loading.value = false
})
})
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>搜索示例</h2>
<input
value={keyword}
onInput={(e) => {
keyword.value = (e.target as HTMLInputElement).value
}}
placeholder="输入关键词搜索"
style={{
width: '100%',
padding: '8px 12px',
fontSize: '14px',
boxSizing: 'border-box'
}}
/>
{loading.value && <p style={{ color: '#999' }}>搜索中...</p>}
<ul style={{ paddingLeft: '20px' }}>
{results.value.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
)
}
createApp(App).mount('#app')API 参考
typescript
// 监听 ref
declare function watch<T extends Ref>(
source: T,
callback: (newValue: T['value'], oldValue: T['value'], onCleanup: WatcherOnCleanup) => void,
options?: WatchOptions
): Watcher
// 监听 getter
declare function watch<T>(
source: () => T,
callback: (newValue: T, oldValue: T, onCleanup: WatcherOnCleanup) => void,
options?: WatchOptions
): Watcher
// 自动追踪副作用
declare function watchEffect(
effect: (onCleanup: WatcherOnCleanup) => void,
options?: WatcherOptions
): EffectWatcher
declare interface WatchOptions {
immediate?: boolean // 是否立即执行,默认 false
deep?: boolean | number // 是否深度监听,默认 false
once?: boolean // 是否只执行一次,默认 false
flush?: 'pre' | 'post' | 'sync' // 执行时机,默认 'pre'
}下一步
- 条件渲染 — 学习如何根据条件显示不同内容