响应性 API:核心 {#reactivity-api-core}

:::info 相关内容 要更好地了解响应性 API,推荐阅读下面几个指南中的章节:

ref() {#ref}

接受一个值,将其用作内部值来返回一个响应式的、可更改的 ref 对象。只有一个属性 .value 用来指向内部的值。

  • 类型

    1. function ref<T>(value: T): Ref<UnwrapRef<T>>
    2. interface Ref<T> {
    3. value: T
    4. }
  • 详细信息

    ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发相应的副作用。

    如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应性的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。

    若要避免这种深层次的转换,请使用 shallowRef() 来替代。

  • 示例

    1. const count = ref(0)
    2. console.log(count.value) // 0
    3. count.value++
    4. console.log(count.value) // 1
  • 相关内容:

computed() {#computed}

接受一个计算函数,返回一个只读的响应式 ref 对象,即计算函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

  • 类型

    1. // 只读
    2. function computed<T>(
    3. getter: () => T,
    4. // 查看下方的 "计算属性调试" 链接
    5. debuggerOptions?: DebuggerOptions
    6. ): Readonly<Ref<Readonly<T>>>
    7. // 可写的
    8. function computed<T>(
    9. options: {
    10. get: () => T
    11. set: (value: T) => void
    12. },
    13. debuggerOptions?: DebuggerOptions
    14. ): Ref<T>
  • 示例

    创建一个只读的计算属性 ref:

    1. const count = ref(1)
    2. const plusOne = computed(() => count.value + 1)
    3. console.log(plusOne.value) // 2
    4. plusOne.value++ // 错误

    创建一个可写的计算属性 ref:

    1. const count = ref(1)
    2. const plusOne = computed({
    3. get: () => count.value + 1,
    4. set: (val) => {
    5. count.value = val - 1
    6. }
    7. })
    8. plusOne.value = 1
    9. console.log(count.value) // 0

    调试:

    1. const plusOne = computed(() => count.value + 1, {
    2. onTrack(e) {
    3. debugger
    4. },
    5. onTrigger(e) {
    6. debugger
    7. }
    8. })
  • 相关内容:

reactive() {#reactive}

返回一个对象的响应式代理。

  • 类型

    1. function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
  • 详细信息

    响应式转换时“深层”的:它会影响到内部所有层次的属性。一个响应式对象也可以深层地解包任何为 ref 的属性,同时保持响应性。

    同时值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中为 ref 的元素时,不会执行 ref 的解包。

    若为避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,请使用 shallowReactive() 作替代。

    返回的对象以及其中嵌套的对象都会通过 ES Proxy 包裹,因此 不等于 源对象,建议只使用响应式代理,避免依赖于原始对象。

  • 示例

    创建一个响应式对象:

    1. const obj = reactive({ count: 0 })
    2. obj.count++

    ref 的解包:

    1. const count = ref(1)
    2. const obj = reactive({ count })
    3. // ref 会被解包
    4. console.log(obj.count === count.value) // true
    5. // 会更新 `obj.count`
    6. count.value++
    7. console.log(count.value) // 2
    8. console.log(obj.count) // 2
    9. // 也会更新 `count` ref
    10. obj.count++
    11. console.log(obj.count) // 3
    12. console.log(count.value) // 3

    注意当访问到某个响应式数组或 Map 这样的原生集合类型中为 ref 的元素时,不会执行 ref 的解包:

    1. const books = reactive([ref('Vue 3 Guide')])
    2. // 这里需要 .value
    3. console.log(books[0].value)
    4. const map = reactive(new Map([['count', ref(0)]]))
    5. // 这里需要 .value
    6. console.log(map.get('count').value)

    将一个 ref 赋值给为一个 reactive 属性时,该 ref 会被自动解包:

    1. const count = ref(1)
    2. const obj = reactive({})
    3. obj.count = count
    4. console.log(obj.count) // 1
    5. console.log(obj.count === count.value) // true
  • 相关内容:

readonly() {#readonly}

接受一个对象(不论是响应式还是一般的)或是一个 ref,返回一个原值的只读代理。

  • 类型

    1. function readonly<T extends object>(
    2. target: T
    3. ): DeepReadonly<UnwrapNestedRefs<T>>
  • 详细信息

    一个只读的代理是深层生效的,对任何内部层级的属性的访问都是只读的。它与 reactive() 有相同的 ref 解包行为,而解包得的值也同样是只读的。

    要避免深层级的转换行为,请使用 shallowReadonly() 作替代。

  • 示例

    1. const original = reactive({ count: 0 })
    2. const copy = readonly(original)
    3. watchEffect(() => {
    4. // 用来做响应性追踪
    5. console.log(copy.count)
    6. })
    7. // 更改源属性会触发依赖其只读副本的侦听器
    8. original.count++
    9. // 更改该只读副本将会失败,并会得到一个警告
    10. copy.count++ // warning!

watchEffect() {#watcheffect}

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

  • 类型

    1. function watchEffect(
    2. effect: (onCleanup: OnCleanup) => void,
    3. options?: WatchEffectOptions
    4. ): StopHandle
    5. type OnCleanup = (cleanupFn: () => void) => void
    6. interface WatchEffectOptions {
    7. flush?: 'pre' | 'post' | 'sync' // default: 'pre'
    8. onTrack?: (event: DebuggerEvent) => void
    9. onTrigger?: (event: DebuggerEvent) => void
    10. }
    11. type StopHandle = () => void
  • 详细信息

    第一个函数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求(参见下面的示例)。

    第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。

    返回值是一个用来停止该副作用的函数。

  • 示例

    1. const count = ref(0)
    2. watchEffect(() => console.log(count.value))
    3. // -> logs 0
    4. count.value++
    5. // -> logs 1

    Side effect cleanup:

    1. watchEffect(async (onCleanup) => {
    2. const { response, cancel } = doAsyncWork(id.value)
    3. // `cancel` 会在 `id` 更改时调用
    4. // 因此,之前的等待中的请求
    5. // 若没有完成将被取消
    6. onCleanup(cancel)
    7. data.value = await response
    8. })

    停止侦听器:

    1. const stop = watchEffect(() => {})
    2. // 当不再需要此侦听器时:
    3. stop()

    Options:

    1. watchEffect(() => {}, {
    2. flush: 'post',
    3. onTrack(e) {
    4. debugger
    5. },
    6. onTrigger(e) {
    7. debugger
    8. }
    9. })
  • 相关内容:

watchPostEffect() {#watchposteffect}

watchEffect() 使用 flush: 'post' 选项时的别名。

watchSyncEffect() {#watchsynceffect}

watchEffect() 使用 flush: 'sync' 选项时的别名。

watch() {#watch}

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

  • 类型

    1. // 侦听单个来源
    2. function watch<T>(
    3. source: WatchSource<T>,
    4. callback: WatchCallback<T>,
    5. options?: WatchOptions
    6. ): StopHandle
    7. // 侦听多个来源
    8. function watch<T>(
    9. sources: WatchSource<T>[],
    10. callback: WatchCallback<T[]>,
    11. options?: WatchOptions
    12. ): StopHandle
    13. type WatchCallback<T> = (
    14. value: T,
    15. oldValue: T,
    16. onCleanup: (cleanupFn: () => void) => void
    17. ) => void
    18. type WatchSource<T> =
    19. | Ref<T> // ref
    20. | (() => T) // getter
    21. | T extends object
    22. ? T
    23. : never // 响应式对象
    24. interface WatchOptions extends WatchEffectOptions {
    25. immediate?: boolean // 默认:false
    26. deep?: boolean // 默认:false
    27. flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
    28. onTrack?: (event: DebuggerEvent) => void
    29. onTrigger?: (event: DebuggerEvent) => void
    30. }

    为了便于阅读,对类型进行了简化。

  • 详细信息

    watch() 默认是懒侦听的,即回调函数仅会在侦听源发生变化时才执行。

    第一个参数是侦听器的 。这个来源可以是以下几种:

    • 一个函数,返回一个值
    • 一个 ref
    • 一个响应式对象
    • …或是以上值可能为以上三种类型的数组

    第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新的值、旧的值,以及一个用于注册副作用清理回调的函数。清理副作用的回调会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

    当侦听多个来源时,回调函数接受的新、旧值都是所对应源各个值的一个数组。

    第三个可选的参数是一个对象,支持以下这些选项:

    • immediate:在侦听器创建时立即触发回调。这第一次调用时旧的值会是 undefined
    • deep:如果源是对象,强制深度遍历,以便在深层级变更时启动回调。相关内容请看 深层侦听器 一节。
    • flush:调整回调函数的刷新时机。相关内容请看 回调的刷新时机 一节。
    • onTrack / onTrigger:调试侦听器的依赖。相关内容请看 调试侦听器 一节。

    watchEffect() 相比,watch() 使我们可以:

    • 懒执行副作用;
    • 更加明确是应该由哪个状态触发侦听器重新执行;
    • 可以访问所侦听状态的前一个值和当前值。
  • 示例

    侦听源是一个函数:

    1. const state = reactive({ count: 0 })
    2. watch(
    3. () => state.count,
    4. (count, prevCount) => {
    5. /* ... */
    6. }
    7. )

    侦听源是一个 ref:

    1. const count = ref(0)
    2. watch(count, (count, prevCount) => {
    3. /* ... */
    4. })

    当侦听多个来源时,回调函数接受的新、旧值都是所对应源各个值的一个数组:

    1. watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
    2. /* ... */
    3. })

    当使用函数作源时,侦听器只在此函数的返回值发生变化时才会启动。如果你想让回调在深层级变更时也能启动,你需要明确地用 { deep: true } 强制侦听器进入深层级模式。

    1. const state = reactive({ count: 0 })
    2. watch(
    3. () => state,
    4. (newValue, oldValue) => {
    5. // newValue === oldValue
    6. },
    7. { deep: true }
    8. )

    当直接侦听一个响应式对象时,侦听器自动处于深层级模式:

    1. const state = reactive({ count: 0 })
    2. watch(state, () => {
    3. /* 深层级变更状态所触发的回调 */
    4. })

    watch()watchEffect() 享有相同的刷新时机和调试选项:

    1. watch(source, callback, {
    2. flush: 'post',
    3. onTrack(e) {
    4. debugger
    5. }
    6. })
  • 相关内容: