watch 详解

watch() 用于监听响应式数据的变化并执行回调。Vitarx 提供了多种 watch 变体,支持监听不同类型的数据源,以及 watchEffect 系列函数用于自动追踪依赖。

watch() 四种重载

1. 监听 Ref

直接监听一个 ref(ValueRef 或 ShallowRef),当 .value 变化时触发回调:

tsx
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 选项使用:

tsx
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 对象时,回调中的 newValueoldValue 指向同一个对象(因为对象是引用类型,修改的是同一个代理对象)。如果需要获取旧值快照,请使用 getter 函数方式。

3. 监听 getter 函数

通过一个 getter 函数指定要监听的值,getter 内访问的响应式数据都会被追踪:

tsx
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 中没有访问 age

4. 监听多数据源

传入一个数组,同时监听多个数据源。任意一个变化都会触发回调,回调参数也是数组:

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}`)
})

firstName.value = '李' // 输出: "张三 → 李三"

数组中可以混合使用 ref、reactive 对象和 getter 函数:

tsx
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

tsx
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 时,第一次回调的 oldValueundefined

deep

是否深度监听。当监听 reactive 对象时,需要设置 deep: true 才能追踪嵌套属性的变化。默认为 false

tsx
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 也可以传入数字,指定遍历深度:

tsx
watch(state, callback, { deep: 2 }) // 只遍历 2 层深度

once

是否只监听一次。回调执行一次后自动停止监听。默认为 false

tsx
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'同步执行——数据变化后立即执行回调
tsx
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 不同,它不需要指定监听源——函数内访问的响应式数据都会被自动追踪。

tsx
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 的区别

特性watchwatchEffect
监听源需要显式指定自动追踪
是否立即执行默认否(需 immediate: true
获取旧值可以不可以
适用场景需要精确控制监听目标依赖关系简单,不需要旧值

watchPostEffect()

watchPostEffect()watchEffectflush: 'post' 版本——副作用在视图更新后执行:

tsx
import { ref, watchPostEffect } from 'vitarx'

const count = ref(0)

watchPostEffect(() => {
  // DOM 更新后执行
  console.log('count 变化了:', count.value)
})

等价于:

tsx
watchEffect(
  () => {
    console.log('count 变化了:', count.value)
  },
  { flush: 'post' }
)

watchSyncEffect()

watchSyncEffect()watchEffectflush: 'sync' 版本——数据变化后同步执行副作用:

tsx
import { ref, watchSyncEffect } from 'vitarx'

const count = ref(0)

watchSyncEffect(() => {
  // 同步执行——数据变化后立即执行
  console.log('count 变化了:', count.value)
})

onCleanup 清理副作用

watchwatchEffect 的回调都支持注册清理函数——在下一次回调执行前或监听器被停止时调用。用于清理上一次副作用产生的资源(如定时器、网络请求等)。

watch 中的 onCleanup

tsx
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

tsx
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() — 停止监听

tsx
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() — 暂停和恢复

tsx
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 的示例:

tsx
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')

下一步