第五节:带你全面理解 vue3 中 computed, watch, watchEffect 组合式API的使用

前言:

上一章, 带大家分析了vue3核心响应式API中的三个, 即reactive,ref, readonly.

本章将会带大家分另外几个工作中比较常用的组合式API.

1. computed 计算属性

vue2中, 我们是通过computed选项添加计算属性的, 关于计算属性的本质, 这里就不过多阐述了, 如果还有不了解的同学, 可以去看vue2专栏中,关于computed计算属性讲解

1.1. computed 基本使用

computed组合式API, 接受一个 getter 函数作为参数,返回一个只读的响应式 ref对象。该 ref通过 .value 暴露 getter 函数的返回值。

这句话看着有点拗口, 我们通过示例来分析computedAPI的使用.

示例:

<template>
  <div>
    <h2>computed</h2>
    <div>{{ userName }}</div>
    <button @click="change">修改依赖</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed, ref } from 'vue'

export default defineComponent({
  setup() {
    // 计算属性依赖
    const firstName = ref("张")
    const lastName = ref("三")

    // 计算属性返回的ref数据
    const userName = computed(() => {
      console.log("computed")
      return firstName.value + ' ' + lastName.value
    })
    console.log("userName", userName)

    // 修改计算属性的依赖
    const change = () => {
      firstName.value = "李"
    }

    return { userName, change }
  },
})
</script>

控制台输出结果:


image.png

接下来我们对代码示例进行分析.主要从以下以下几点分析:

computed 返回值分析

通过控制台输出的computedAPI 返回的结果, 你会发现, 结构与refAPI 创建响应式数据结构极度相似. 这也说明了一点, computed返回的数据也是具有响应性的, 同时使用方式也与ref数据, 通过.value属性进行操作.

computed 参数分析

让我们将目光移入computedAPI 的参数部分, 参数是一个回调函数, 这个回调函数返回一个数据, 返回的数据是有两个ref数据拼接而成. 这两个具有响应性的ref数据我们就称为是computed数据的依赖.

这个回调函数就是所谓的getter函数. 函数的返回值,就是computed返回的ref对象的value属性值.

computed参数的回调函数会在初始时自动调用一次, 后续只有当依赖项数据发生变化,才会促使参数getter函数重新执行获取最新的数据. 否则computed返回ref数据无论使用多少次, 结果都是一样的.

computed 返回数据在模板上使用

computedAPI 返回的ref数据在使用上与refAPI 创建的数据完全一致. 在模板上使用会自动解包, 因此我们不需要在模板中使用.value

示例代码中的修改逻辑

针对示例代码中修改数据的逻辑, 主要在模板上绑定了click事件, 当事件被触发时, 会执行事件处理函数, 即change函数., ,在change函数中修改了具有响应性的ref数据, 即firstName,

firstName作为计算属性computed参数getter函数 依赖项. 根据计算属性特性, 当依赖项发生变化, 会自动执行getter函数, 返回计算后最新的数据.

也就意味着userName这个具有响应性的ref数据发生了变化, 进而触发页面模板重新渲染, 更新视图

据此总结: computed 返回的数据也是具有响应性的

1.2. computed 计算属性设置

vue2中, 计算属性参数可以是一个函数, 如果是一个函数表示getter函数. 但如果参数是一个对象, 对象可以具有getter函数和setter函数.

通过vue3computedAPI 也可以接受一个带有 gettersetter 函数的对象来创建一个可写的 ref 对象。


示例:

<template>
  <div>
    <h2>computed</h2>
    <div>{{ userName }}</div>
    <button @click="change">修改依赖</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed, ref } from 'vue'

export default defineComponent({
  setup() {
    // 计算属性依赖
    const firstName = ref("张")
    const lastName = ref("三")

    // 计算属性返回的ref数据
    const userName = computed({
      get() {
        console.log('获取计算属性')
        return firstName.value + ' ' + lastName.value
      },
      set(value) {
        console.log('设置计算属性',value)
        const nameArr = value.split(' ')
        firstName.value = nameArr[0]
        lastName.value = nameArr[1]
      }

    })

    // 修改计算属性的依赖
    const change = () => {
      // firstName.value = "李"
      userName.value = '李 四'
      console.log(userName.value)
    }

    return { userName, change }
  },
})
</script>


1.3. computed 最佳使用

官网对此也有描述, computed计算属性最佳使用方式有两点:

  • 计算属性getter函数中不应该修改其他状态数据,或异步操作. getter函数的本质就是根据依赖项计算最新的结果.
  • 计算属性本身就是根据依赖项生成的快照信息, 具有一定的缓存作用, 因此修改的意义不大. 所以不建议使用setter函数.

2. watch 侦听器

watch侦听器的作用,在vue2中也分析过. 就是监听数据的变化, 当数据发生变化时处理一些事情

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

2.1. watch 基本使用

侦听器watch api 接受三个参数

  1. 第一个参数: 侦听数据源,
  2. 第二个参数: 侦听数据源发生变化时执行的回调函数,回调函数接受三个参数: 新值,旧值,以及清理副作用的回调函数
  3. 第三个参数: 一个设置侦听配置对象, 为可选参数

示例:

<template>
  <div>
    <h2>watch</h2>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref,  watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)

    // 侦听响应数据变化
    watch(
      count,
      () => {
        console.log('count', count.value)
      }
    )

    // 修改监听数据源
    const change = () => {
      count.value = 100
    }
    return { change }
  },

})
</script>

示例描述:

  1. 示例中watch的第一个参数 count 为侦听数据源
  2. watch 第二个参数是一个回调函数, 当count值发生变化时执行第二个参数回调函数


2.2. watch 默认是懒侦听的

watch 侦听一个数据源, 在侦听时默认是懒侦听的, 也就是初始时不会触发侦听器的回调函数,只有当数据源发生变化时才会调用回调函数

示例:

<template>
  <div>
    <h2>watch</h2>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref,  watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)

    // 侦听响应数据变化
    watch(
      count,
      () => {
        console.log('count', count.value)
      }
    )

    // 修改监听数据源
    const change = () => {
      count.value = 100
    }
    return { change }
  },

})
</script>

通过示例会发现,初始时watch 第二个参数回调函数不会执行, 只有当count 修改时才会触发侦听器


2.3. watch 侦听数据源

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

  1. 一个函数,返回一个值

  2. 一个 ref

  3. 一个响应式对象

  4. 或是由以上类型的值组成的数组

侦听源为ref数据

示例:

<template>
  <div>
    <h2>watch</h2>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref,  watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)

    // 侦听响应数据变化
    watch(
      count,
      () => {
        console.log('count', count.value)
      }
    )

    // 修改监听数据源
    const change = () => {
      count.value = 100
    }
    return { change }
  },

})
</script>

示例中countref 数据, 因此当count 变化时,会触发响应,执行watch 侦听的回调函数

侦听源为reactive响应对象

示例

<template>
  <div>
    <h2>watch</h2>
    {{ user }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent,  watch, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个reactive响应数据
    const user = reactive({ name: '张三', age: 20 })

    // 侦听响应数据变化
    watch(
      user,
      () => {
        console.log('user', user)
      }
    )

    // 修改监听数据源
    const change = () => {
      user.name = '李四'
    }
    return { user, change }
  },

})
</script>

示例中, watch 侦听器的数据源是一个reactive响应式数据, 因此当数据发生变化时,watch 会执行侦听器的回调函数

侦听源是一个函数

watch 侦听数据源只能是响应对象, 如果我们项监听响应对象某个属性的变化,如果属性值是一个原始类型类型的值, 那么就会报ts就会报错

此时就需要使用函数式写法, 函数返回需要监听的数据

示例

<template>
  <div>
    <h2>watch</h2>
    {{ user }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const user = reactive({ name: '张三', age: 20 })

    // 侦听源是函数写法
    watch(
      () => user.name,
      () => {
        console.log('user', user)
      }
    )

    // 修改监听数据源
    const change = () => {
      user.name = '李四'
    }
    return { user, change }
  },

})
</script>


侦听数据源是一个数组

以上三种用法都是监听一个数据的变化, 如果希望监听多个数据的变化, 可以使用数组的形式

侦听数据源可以是以上三种组成的数组

示例:

<template>
  <div>
    <h2>watch</h2>
    {{ user }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)
    const user = reactive({ name: '张三', age: 20 })
    const person = reactive({ name: '小明' })

    // 侦听多个数据源:ref, 响应对象, 函数组成的数组
    watch(
      [count, user, () => person.name],
      () => {
        console.log('监听触发了')
      }
    )

    // 修改任意一个数据源,都会触发侦听
    const change = () => {
      count.value = 100
      // user.name = '李四'
      // person.name = '李四'
    }
    return { user, change }
  },

})
</script>


2.4. watch 回调函数

watch函数的第二个参数就是回调函数, 也就是说当侦听源发生变化时,执行回调函数

此回调函数接受以下几个参数:

  1. 侦听数据源最新的值
  2. 侦听数据源变化前的旧值
  3. 清理副作用的回调函数

新旧值参数

首先来看一下新值和旧值两个参数, 这是在使用watch时比较常用到的参数

示例:

<template>
  <div>
    <h2>watch</h2>
    {{ count }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)


    // 侦听响应数据变化
    watch(
      count,
      (nv, ov) => {
        console.log('新值', nv)  // 100
        console.log('旧值', ov)  // 10
      }
    )

    // 修改监听数据源
    const change = () => {
      count.value = 100
    }
    return { count, change }
  },

})
</script>

示例代码中watch 侦听一个ref数据的变化, 当ref数据发生变化时, 执行watch 的回调函数(即watch的第二个参数)

这个回调函数接受两个参数, 第一个是ref 数据的新值, 第二个参数是侦听ref 数据的旧值

清理副作用的参数

接下来我们看一下回调函数第三个参数的使用: 清理副作用

watch 侦听的回调函数中第三个参数是清理副作用的函数, 此函数接受一个函数作为参数

示例:

<template>
  <div>
    <h2>watch</h2>
    {{ count }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)

    // 延迟打印参数
    const printInfo = (val: any) => {
      let timer = setTimeout(() => {
        console.log('val', val)
      }, 3000)


      // 清理定时器
      const clearTimer = () => {
        clearTimeout(timer)
      }
      return {
        clearTimer
      }
    }

    
    // 侦听ref数据变化
    watch(
      count,
      (nv, ov, onCleanup) => {
        // 执行printInfo函数, 返回一个清理定时器的函数
        const { clearTimer } = printInfo(nv)

        // 清理副作用函数
        onCleanup(clearTimer)
      }
    )

   

    // 修改监听数据源
    const change = () => {
      count.value++
    }
    return { count, change }
  },

})
</script>

示例中, 当count 数据发生变化时,watch 执行回调函数, 在回调函数中, 调用printInfo 传入新值,

printInfo 函数中, 延迟三秒打印传入的count值,并返回一个用与关闭延迟定时器的函数

watch 回调函数中通过解构的方式获取到关闭定时器的函数,并作为参数传给了回调函数的第三个参数

此时当时间不满三秒时, count再次发生变化, 此时又一次调用watch 回调函数,在此回调函数中就会清理上一次的副作用, 关闭上一次的定时器


2.5. watch 选项对象

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

  1. immediate: 在侦听器创建时立即触发回调。第一次调用时旧值是 undefined

  2. deep: 如果源是对象,强制深度遍历,以便在深层级变更时触发回调

  3. flush: 调整回调函数的刷新时机

immediate 初始执行监听函数

初始化立即执行watch侦听器的回调函数

示例:

<template>
  <div>
    <h2>watch</h2>
    {{ count }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)


    // 侦听响应数据变化
    watch(
      count,
      (nv, ov) => {
        console.log('新值', nv)  // 100
        console.log('旧值', ov)  // 10
      },
        // 选项对象
      {
        immediate: true // 初始监听
      }
      
    )

    // 修改监听数据源
    const change = () => {
      count.value = 100
    }
    return { count, change }
  },

})
</script>

当我们没有使用immediate 选项时, 默认值为false, 此时初始侦听器回调函数不会执行, 只有当侦听源发生变化时才会触发侦听器, 调用回调函数, 新增为修改后的值, 旧值为修改之前的值

如果使用immediate 选项,组件初始化时就会执行侦听器回调函数, 此时回调函数新增为侦听源初始值, 旧值为undefined


deep 深度监听选项

deep 选项默认值为false, 如果设置为true时, 则表示深度监听, 即侦听源对象中属性的变化也会被侦听到, 执行回调函数

示例

<template>
  <div>
    <h2>watch</h2>
    {{ user }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed, ref, getCurrentInstance, watch, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const user = ref({ name: '张三', age: 20 })

    watch(
      user,
      (nv, ov,) => {
        console.log('侦听器触发了', nv, ov)
      },
      {
        deep: true
      }
    )

    // 修改监听数据源
    const change = () => {
      user.value.name = '李四'
    }
    return { user, change }
  },

})
</script>

示例中如果没有添加deep选项时, 修改ref 数据中name属性不会触发侦听器, 只有整体修改时才会触发侦听器, 如下:

user.value = { name: "李四", age: 28 }


当使用deep 选项, 值设置为true时, 表示深度监听, 此时通过如下修改依然可以触发侦听器

user.value.name = '李四'


flush: 调整回调函数的刷新时机

当你更改了响应式状态,它可能会同时触发 Vue 组件更新(视图更新)和侦听器回调。

默认情况下,侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM将是被 Vue 更新之前的状态。

示例:

<template>
  <div>
    <h2>watch</h2>
    <div ref="userRef"> {{ user }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const user = reactive({ name: '张三', age: 20 })

    // 获取dom节点
    const userRef = ref()

    watch(
      user,
      (nv, ov,) => {
        console.log('侦听器触发了', nv, ov)
        console.log('获取dom节点', userRef.value)
      },
    )


    // 修改监听数据源
    const change = () => {
      user.name = '李四'
    }
    return { user, change, userRef }
  },

})
</script>

控制台输出效果:


image.png

通过示例的运行结果, 可以很明确的看到:

修改数据时,watch 监听的回调函数已经执行, 但是组件视图并没有更新,因此获取的dom节点显示的依然是之前的内容,

侦听器回调函数执行完毕后,才会执行组件视图的更新, 你也可以使用onBeforeUpdate 生命周期钩子函数验证.

watch侦听器的回调函数会先于onBeforeUpdate钩子函数的回调函数执行.

如果想在侦听器回调中能访问被 Vue 组件更新之后的 DOM,你需要指明 flush: 'post' 选项:

示例:

<template>
  <div>
    <h2>watch</h2>
    <div ref="userRef"> {{ user }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const user = reactive({ name: '张三', age: 20 })

    // 获取dom节点
    const userRef = ref()

    watch(
      user,
      (nv, ov,) => {
        console.log('侦听器触发了', nv, ov)
        console.log('获取dom节点', userRef.value)
      },
      {
        flush: "post"
      }
    )


    // 修改监听数据源
    const change = () => {
      user.name = '李四'
    }
    return { user, change, userRef }
  },

})
</script>

此时修改数据时,控制台输出:


image.png

在实例中,添加了flush:'post' 选项后, 侦听源修改后, 先更新了vue组件视图, 其次才调用侦听器的回调函数.

如果你此时使用了onUpdated钩子函数, 你会发现具有flush:'post'选项的watch 回调函数,在组件更新后, 但在onUpdated钩子函数前执行.


2.6. watch 返回值: 关闭侦听器

watch 方法返回一个用于关闭侦听器的函数

示例:

<template>
  <div>
    <h2>watch</h2>
    {{ count }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)


    // 侦听响应数据变化
    // watch 返回值stop 是一个用于关闭侦听器的函数
    cosnt stop = watch(
      count,
      (nv, ov) => {
        console.log('新值', nv)  // 100
        console.log('旧值', ov)  // 10
      },
        // 选项对象
      {
        immediate: true // 初始监听
      }    
    )

    // 3s 以后关闭侦听器
    setTimeout(() => {
      stop()
    }, 3000)

    // 修改监听数据源
    const change = () => {
      count.value++
    }
    return { user, change }
  },

})
</script>

watch侦听器函数返回值是一个用于关闭侦听器的函数, 只要这个函数一执行, 侦听器就会被关闭调, 侦听源数据的变化, 不会在引发watch回调函数的执行.

3. watchEffect

vue2只有一个watch选项用于侦听数据的变化.

但在vue3中, 除了watch侦听数据变化外, 还新增了watchEffect API , watchEffect接受一个回调函数, 会立即执行一次回调函数, 并且收集回调函数中的依赖数据, 以后只要依赖数据发生变化, 都会重新执行回调函数.

3.1. watchEffect 基本使用

watchEffect 接收两个参数:

  1. 监听回调函数,必传参数, 依赖发生变化执行, 默认初始执行
  2. 监听的配置对象

示例:

<template>
  <div>
    <h2>watch</h2>
    <div> fullName:{{ fullName }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue'

export default defineComponent({
  setup() {
    // state
    const firstName = ref('李')
    const lastName = ref('三')
    const fullName = ref('李 三')

    // watchEffect 回调函数依赖发生变化时重新执行
    watchEffect(() => {
      console.log('watchEffect')
      fullName.value = firstName.value + ' ' + lastName.value
    })


    // 修改监听数据源
    const change = () => {
      firstName.value = '张'
    }
    return {  change, fullName }
  },

})
</script>


3.2. watcEffect 回调函数

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


示例:

<template>
  <div>
    <h2>watch</h2>
    {{ count }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)

    // 延迟打印参数
    const printInfo = (val: any) => {
      let timer = setTimeout(() => {
        console.log('val', val)
      }, 3000)


      // 清理定时器
      const clearTimer = () => {
        clearTimeout(timer)
      }
      return {
        clearTimer
      }
    }

    
    // 侦听ref数据变化
    watch(
      count,
      (nv, ov, onCleanup) => {
        // 执行printInfo函数, 返回一个清理定时器的函数
        const { clearTimer } = printInfo(nv)

        // 清理副作用函数
        onCleanup(clearTimer)
      },
      {
        immediate: true
      }
    )

    // 上面的写法等价于下面watchEffect 写法
    watchEffect((onCleanup) => {
      // fullName2.value = firstName.value + ' ' + lastName.value
      const { clearTimer } = printInfo(count.value)
      onCleanup(clearTimer)
    })


   

    // 修改监听数据源
    const change = () => {
      count.value++
    }
    return { user, change }
  },

})
</script>


3.3. watchEffect 第二个参数: 配置对象

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

默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post'将会使侦听器延迟到组件渲染之后再执行。


示例:

<template>
  <div>
    <h2>watch</h2>
    <div ref="userRef"> {{ user }}</div>
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch, reactive } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const user = reactive({ name: '张三', age: 20 })

    // 获取dom节点
    const userRef = ref()

    watch(
      user,
      (nv, ov,) => {
        console.log('侦听器触发了', nv, ov)
        console.log('获取dom节点', userRef.value)
      },
      {
        flush: "post",
        immediate: true
      }
    )

    // 等价于以下watchEffect用法
    watchEffect(() => {
      console.log('侦听器触发了', user.value)
      console.log('获取dom节点', userRef.value)
    },{
      flush: "post",
    })


    // 修改监听数据源
    const change = () => {
      user.name = '李四'
    }
    return { user, change, userRef }
  },

})
</script>


3.4. watchEffect 返回值:关闭侦听器的函数

watchEffect 返回值与watch 函数一样,都是用于关闭侦听器的函数

示例:

<template>
  <div>
    <h2>watch</h2>
    {{ count }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10)


    // 侦听响应数据变化
    // watch 返回值stop 是一个用于关闭侦听器的函数
    cosnt stop = watch(
      count,
      (nv, ov) => {
        console.log('新值', nv)  // 100
        console.log('旧值', ov)  // 10
      },
        // 选项对象
      {
        immediate: true // 初始监听
      }    
    )

    上面watch 写法等价于以下watchEffect 用法
    const stop = watchEffect(() => {
      console.log('count', count.value)
    })


    // 3s 以后关闭侦听器
    setTimeout(() => {
      stop()
    }, 3000)

    // 修改监听数据源
    const change = () => {
      count.value++
    }
    return { user, change }
  },

})
</script>


4. watch 与 watchEffect 区别

watchwatchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是侦听数据的方式:

  • watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调
  • watchEffect则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁

5. 一次性侦听器

vue3在3.4 版本以后增加了一个选项once, 该选项的默认值为false, 当设置为true时, 被侦听源发生变化时,侦听器的回调只会执行一次, 执行完毕后,立即关闭侦听器。

示例:

<template>
  <div>
    <h2>watch</h2>
    {{ count }}
    <button @click="change">修改数据源</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from "vue";

export default defineComponent({
  setup() {
    // 创建一个响应数据
    const count = ref(10);

    // 侦听响应数据变化
    watch(
      count,
      (nv, ov) => {
        console.log("新值", nv); // 100
        console.log("旧值", ov); // 10
      },
      // 选项对象
      {
        once: true, // 只侦听一次, 执行完就关闭侦听器
      }
    );

    // 修改监听数据源
    const change = () => {
      count.value++;
    };
    return { count, change };
  },
});
</script>


6. 结语

至此, 就把计算属性(computed)和常用的两个侦听器(watch, watchEffect)给大家介绍完毕了.

学完本章你需要能够理解一下知识点

  1. computed, watch,watchEffect三个API的基本使用
  2. 理解计算属性computed参数值的不同 与返回值的使用, 返回值是响应式数据
  3. 认识watch侦听器接受三个参数, 侦听源, 回调函数, 选项对象(可选)
  4. 理解watch侦听数据源的不同写法, 回调函数的三个参数的使用, 选项对象中不同选项的功能
  5. 理解watchEffect侦听器的使用, 以及与watch侦听器的不同


如果觉得这篇文章对你有帮助, 点赞关注不迷路, 你的支持是我的动力!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容