首页 > 其他分享 >vue3 响应式 API:watch()、watchEffect()

vue3 响应式 API:watch()、watchEffect()

时间:2024-08-20 22:24:58浏览次数:15  
标签:函数 person watch value watchEffect API 监视 newValue

watch()

  • 基本概念
    • watch()用于监视响应式数据的变化,并在数据变化时执行相应的回调函数。
    • 可以监视单个响应式数据、多个响应式数据的组合,或者一个计算属性。
  • 返回值
    • 返回一个函数,调用这个函数可以停止监视。
  • 特点
    • watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
    • watch()可以监视单个数据、多个数据的组合或计算属性。
    • 通过深度监视选项,可以方便地监视对象的内部属性变化。
    • 立即执行选项可以在特定情况下立即执行回调函数,提供更多的控制。

watch()的参数说明

watch()函数接收3个参数:数据源、回调函数、选项对象(可选)。

  • 第一个参数:数据源(被监视的数据)
    • ref()定义的数据:基本类型的响应式变量、对象类型的响应式变量。
    • reactive()定义的数据:对象类型的响应式变量。
    • 一个返回响应式数据的函数,比如一个计算属性的 getter 函数。
    • 一个包含上述内容的数组。

  • 第二个参数:回调函数
    • 当数据源发生变化时,会调用这个回调函数。
    • 回调函数接收三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数onInvalidate
      • 如果只监视一个数据源,那么新值和旧值分别对应数据源变化后的新值和变化前的旧值。
      • 如果监视多个数据源,那么新值和旧值分别是一个包含新数据源值的数组和一个包含旧数据源值的数组。
      • onInvalidate: 这是一个用于注册副作用清理的回调函数。这个回调函数可以在 watch() 的回调函数内部调用,传入一个清理函数作为参数。这个清理函数会在下次 watch() 回调执行之前被调用,以便清理上一次执行产生的副作用。
    • 示例:(newValue, oldValue) => { /* 处理变化的逻辑 */ }
      (newValue, oldValue, onInvalidate) => { /* 处理变化的逻辑, 用于注册副作用清理的回调函数 */ }

  • 第三个参数:选项对象(可选)
    • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器
      • 布尔值,默认值为false
      • 如果设置为true,则会进行深度监视,即当监视的是一个对象时,对象的内部属性发生变化也会触发回调函数。
    • immediate:侦听器创建时立即触发回调。第一次调用时旧值是 undefined
      • 布尔值,默认值为false
      • 如果设置为true,则在创建 watch() 时立即调用回调函数,此时旧值是 undefined,新值是当前值。
    • flush:控制回调函数的执行时机。参考回调的刷新时机watchEffect()
      • 默认值是'pre',表示在 DOM 更新之前执行回调;
      • 'post'表示在 DOM 更新之后执行回调;
      • 'sync'表示同步执行回调,即立即执行回调函数,并且在响应式数据变化时同步更新视图。
    • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器
      • onTrack(用于调试):当响应式数据被读取并作为依赖被追踪时,这个函数会被调用。可以在这个函数中记录哪些数据被读取了,以便进行调试和分析依赖关系。
      • onTrigger(用于调试):当响应式数据发生变化并触发依赖时,这个函数会被调用。可以在这个函数中记录哪些数据发生了变化,以便进行调试和分析依赖关系。
    • once:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
      • 布尔值,默认值为false
      • 如果设置为true,回调函数只会执行一次。侦听器将在回调函数首次运行后自动停止。

示例

监视ref()定义的【基本类型】数据

语法: watch(变量名, (newValue, oldValue) => {})
监视时直接写变量名,其本质上监视的是.value

<template>
  <div>
    <div>count: {{ count }}</div>
    <button @click="addCount">点击 count+1</button>
  </div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';

let count = ref(0)

const addCount = () => {
  count.value++
}

// watch 监视的是 ref定义的数据:count
watch(count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变为 ${newValue}`);
})
</script>

使用watch()监视count这个响应式变量的变化。当count的值发生变化时,回调函数会被执行,打印出旧值和新值。

监视ref()定义的【对象类型】数据

语法: watch(变量名, (newValue, oldValue) => {})
监视时直接写变量名,监视的是对象的引用地址值,如果想监视对象的内部属性变化,要手动开启深度监视(deep: true

  • 如果修改的是ref()定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象(同一个引用地址)。
  • 如果修改整个ref()定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

示例:

<template>
  <div>
    <p>姓名: {{ person.name }}</p>
    <p>年龄: {{ person.age }}</p>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改整个人</button>
  </div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';

const person = ref({
  name: '张三',
  age: 18
});

const changeName = () => {
  person.value.name += '哈'  
}
const changeAge = () => {
  person.value.age ++   
}

const changePerson = () => {
  person.value = { name: '李四', age: 17 }
}

// changeName、changeAge修改person的内部属性,不会改变person的引用地址,不会触发 watch
// changePerson 对 person 重新赋值,改变了person的引用地址,触发watch
watch(person, (newValue, oldValue) => {
  console.log('newValue: ',  newValue)
  console.log('oldValue: ', oldValue)
})
</script>

如果想要深度监视对象的内部属性变化,可以在watch()第三个参数(选项对象)中设置deep: true

watch(person, (newValue, oldValue) => {
  console.log('newValue: ',  newValue)
  console.log('oldValue: ', oldValue)
},{ deep: true })

开启深度监视(deep: true)后,修改personname属性、age属性,触发了watch()监视。
但是,watch()监视到的newValueoldValue 都是新值:
在这里插入图片描述
这是因为newValueoldValue 都是指向同一个引用地址:person的引用地址。

为了更准确地区分新旧值,可以在修改对象内部属性之前,先对旧对象进行一个副本的创建,这样在watch()回调函数中,就可以把副本作为旧值来处理。

监视reactive()定义的【对象类型】数据

监视reactive()定义的对象类型的响应式变量,watch()默认开启了深度监视,且这个深度监视是无法关闭的(设置deep: false无效)。

<template>
  <div>
    <p>姓名: {{ person.name }}</p>
    <p>年龄: {{ person.age }}</p>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改整个人</button>
  </div>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';

const person = reactive({
  name: '张三',
  age: 18
});

const changeName = () => {
  person.name += '哈'  
}
const changeAge = () => {
  person.age ++   
}

const changePerson = () => {
  // Object.assign 方法会合并源对象到目标对象上。
  // 当对一个 reactive 对象使用 Object.assign 时,对象的引用地址不会改变。
  // 它实际上是在修改原始对象的属性,而不是完全替换对象。
  Object.assign(person,{ name: '李四', age: 17 })
}


// 监视reactive()定义的对象类型的响应式变量,watch()默认开启了深度监视。
// changeName、changeAge修改person的内部属性,触发 watch
// changePerson 对 person 重新赋值,改变了person的引用地址,触发watch
watch(person, (newValue, oldValue) => {
  console.log('newValue: ',  newValue)
  console.log('oldValue: ', oldValue)
})
</script>

在这里插入图片描述
注意:新值和旧值是同一个值。因为它们都指向同一个引用地址。
changeNamechangeAgechangePerson 从本质上来讲,都是在修改person的属性,没有修改person的引用地址。

监视ref()reactive()定义的【对象类型】数据中的某个属性

  • 若该属性不是【对象类型】属性,需要写成函数形式
    • 当监视ref()reactive()定义的对象中的某个非对象类型属性时,需要写成函数形式。
    • 因为直接监视一个非对象类型的属性时,watch()无法准确追踪其变化。写成函数形式可以确保正确地获取属性值并进行监视。
  • 若该属性【对象类型】属性
    • 可以直接编写属性名进行监视
    • 可以写成函数形式
    • 如果要深度监视对象类型的属性,必须在watch()的选项中设置deep: true
<template>
  <div>
    <p>姓名: {{ person.name }}</p>
    <p>年龄: {{ person.age }}</p>
    <p>职业:{{ person.details.job }}</p>
    <p>年级:{{ person.details.grade }}</p>
    <button @click="changeName">修改名字</button>
    <button @click="changeJob">修改职业</button>
    <button @click="changeGrade">修改年级</button>
    <button @click="changeDetails">修改详细信息</button>
  </div>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';

const person = reactive({
  name: '张三',
  age: 18,
  details: {
    job: 'senior high school student',
    grade: '高三'
  }
});

const changeName = () => {
  person.name += '哈'  
}

const changeJob = () => {
  person.details.job = 'undergraduate'   
}

const changeGrade = () => {
  person.details.grade = '大一'   
}

const changeDetails = () => {
  person.details = {
    job: 'fresh graduate',
    grade: '毕业啦!'
  }
}

// 监视person对象的name属性(基本类型),watch()的第一个参数写成函数式(getter函数)
// () => person.name是一个 getter 函数,它返回person对象的name属性值。
watch(() => person.name, (newName, oldName) => {
  console.log('newName:', newName, 'oldName:', oldName)
});


// 监视details属性中的job属性,watch()的第一个参数写成函数式(getter函数)
// () => person.details.job是一个 getter 函数,它返回person.details.job的值。
watch(() => person.details.job, (newJob, oldJob) => {
  console.log('newJob:', newJob, 'oldJob:', oldJob)
});


// 监视person对象的details属性(对象类型),watch()的第一个参数写成函数式(getter函数)
// () => person.details, 它返回person对象的details属性值。
watch(() => person.details, (newDetails, oldDetails) => {
  console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
});

// 监视person对象的details属性(对象类型),watch()的第一个参数可以直接写变量
watch(person.details, (newDetails, oldDetails) => {
  console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
});

// 监视person对象的details属性(对象类型),watch()的第一个参数可以直接写变量
watch(person.details, (newDetails, oldDetails) => {
  console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
});
</script>

在这个例子中:

  • watch()的第一个参数写成函数式(getter函数)。例如:() => person.name是一个 getter 函数,它返回person对象的name属性值。
  • changeJobchangeGrade不会触发watch(() => person.details, (newDetails, oldDetails) => {}),因为没有开启深度监视。
  • changeDetails 会触发两个监视:
    • watch(() => person.details.job, (newJob, oldJob) => {})
    • watch(() => person.details, (newDetails, oldDetails) => {})

在这里插入图片描述
如果要深度监视对象类型的属性,必须在watch()的选项中设置deep: true
例如,改变person.details.jobperson.details.grade 会 触发watch(person.details)

// 监视person对象的details属性(对象类型),写成函数式
watch(() => person.details, (newDetails, oldDetails) => {
  console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
}, { deep: true });

设置deep: truechangeJobchangeGradechangeDetails都会触发watch(() => person.details, (newDetails, oldDetails) => {})

监视多个响应式数据

<template>
  <div>
    <p>姓名: {{ name }}</p>
    <p>年龄: {{ age }}</p>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';

let name = ref('张三')
let age = ref(18)
const changeName = () => {
  name.value = `${name.value}哈`
}
const changeAge = () => {
  age.value++
}

watch([name, age], (newValue, oldValue) => {
  console.log('newValue', newValue) // newValue是一个数组:[name, age]
  console.log('oldValue', oldValue) // oldValue是一个数组:[name, age]
})

// 当监视多个响应式数据时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
  console.log(`Name changed from ${oldName} to ${newName}`)
  console.log(`Age changed from ${oldAge} to ${newAge}`)
})
</script>

在这个例子中,监视了 nameage 两个响应式数据的变化。当其中任何一个数据发生变化时,回调函数会被执行,打印出两个数据的旧值和新值。

停止监视

使用 watch() 函数创建的监视器,可以通过调用watch()函数的返回值(是一个函数)来停止监视。

<template>
  <div>
    <div>count: {{ count }}</div>
    <button @click="addCount">点击 count+1</button>
  </div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';

let count = ref(0)

const addCount = () => {
  count.value++
}

// watch 监视的是 ref定义的数据:count
// watch()的返回值是一个函数,调用这个函数可以停止监视。
const stopWatcher = watch(count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变为 ${newValue}`);
  // 当newValue > 10,结束监视
  if(newValue > 10) {
    // 调用stopWatcher ,结束监视
    stopWatcher ()
  }
})
</script>

立即执行回调函数

<template>
  <div>
    <p>Counter: {{ counter }}</p>
  </div>
</template>

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

const counter = ref(0)

watch(
  counter,(newValue, oldValue) => {
    console.log(`Counter changed from ${oldValue} to ${newValue}`)
  },
  { immediate: true }
)
</script>

通过设置选项对象的immediate: true,可以让watch()在创建后立即执行一次回调函数,无论被监视的数据是否已经发生变化。
此时,oldValue 的值为 undefined

只执行一次的回调函数

<template>
  <div>
    <p>Counter: {{ counter }}</p>
  </div>
</template>

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

const counter = ref(0)

watch(
  counter,(newValue, oldValue) => {
    console.log(`Counter changed from ${oldValue} to ${newValue}`)
  },
  { once: true }
)
</script>

通过设置选项对象的once: true,回调函数只会执行一次。侦听器将在回调函数首次运行后自动停止。

副作用清除

watch(id, async (newId, oldId, onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // 当 `id` 变化时,`cancel` 将被调用,
  // 取消之前的未完成的请求
  onCleanup(cancel)
  data.value = await response
})

watchEffect()

  • 基本概念
    watchEffect()立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
    • watchEffect()自动追踪函数中使用的响应式数据,并在这些数据发生变化时重新执行函数。
    • watch()不同,watchEffect()不需要明确指定要监视的数据源,它会自动分析函数内部的依赖关系(函数中用到哪些属性,那就监视哪些属性)。
  • 返回值
    • 返回一个函数,调用这个函数可以停止副作用的执行。

watchEffect()参数说明

watchEffect()函数接收以下参数:

  • 回调函数

    • 这是一个必须的参数,它是一个函数,在这个函数内部可以访问响应式数据和执行副作用逻辑。
    • 当使用watchEffect()时,它会立即执行这个回调函数,并在其依赖的响应式数据发生变化时再次执行这个回调函数。
    • 例如:watchEffect(() => { /* 副作用逻辑 */ })

  • 选项对象(可选)

    • flush:控制回调函数的执行时机。参考回调的刷新时机watchEffect()
      • 默认值是'pre',表示在 DOM 更新之前执行回调;
      • 'post'表示在 DOM 更新之后执行回调;
      • 'sync'表示同步执行回调,即立即执行回调函数,并且在响应式数据变化时同步更新视图。
    • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器
      • onTrack(用于调试):当响应式数据被读取并作为依赖被追踪时,这个函数会被调用。可以在这个函数中记录哪些数据被读取了,以便进行调试和分析依赖关系。
      • onTrigger(用于调试):当响应式数据发生变化并触发依赖时,这个函数会被调用。可以在这个函数中记录哪些数据发生了变化,以便进行调试和分析依赖关系。

示例

基本用法

<template>
  <div>
    <p>a: {{ a }}</p>
    <p>b: {{ b }}</p>
    <p>sum: {{ sum }}</p>
    <button @click="incrementA">点击 a+2</button>
    <button @click="incrementB">点击 b+5</button>
    <button @click="incrementSum">点击 sum+1</button>
  </div>
</template>

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

const a = ref(5)
const b = ref(10)
const sum = ref(0)

const incrementA = () => {
  console.log("incrementA");
  a.value = a.value + 2
}

const incrementB = () => {
  console.log("incrementB");
  b.value = b.value + 5
}

const incrementSum = () => {
  console.log("incrementSum");
  sum.value = sum.value + 1
}

// watchEffect()的返回值是一个函数,调用这个函数可以停止监视。
let stopEffect = watchEffect(() => {
  console.log('a:', a.value, 'b:', b.value)
  if(a.value < 20 && b.value < 100){
    sum.value = a.value + b.value
  }

  if(a.value > 20 || b.value > 100) {
    stopEffect()
  }
});

watch(sum, (newSum, oldSum) => {
  console.log('newSum', newSum, 'oldSum', oldSum)
})
</script>

在这个例子中:

  • watchEffect()会立即执行传入的函数,并在ab的值发生变化时重新执行该函数,根据条件计算sum的值。
  • watchEffect()的返回值是一个函数,调用这个函数可以停止监视。
  • incrementSum直接修改sum的值,不会触发watchEffect()。因为sumwatchEffect()是作为计算结果被赋值的,而不是直接被读取使用。
  • watch用于监视sum的变化,当sum的值发生变化时,打印出新值和旧值。
    在这里插入图片描述

使用watchEffect并带有选项对象

watchEffect(() => {
  console.log('a:', a.value, 'b:', b.value)
  if(a.value < 20 && b.value < 100){
    sum.value = a.value + b.value
  }
},
{
  flush: 'post' // 在 DOM 更新之后执行回调 
}
);

在这个例子中,watchEffect()的回调函数会在ab的值发生变化时执行,并在 DOM 更新之后计算sum的值。选项对象中的flush被设置为'post',以控制回调函数的执行时机。

副作用清除

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

watch()watchEffect()的区别

  • 数据源
    • watch()需要明确指定要监视的数据源,可以是一个响应式数据、一个返回响应式数据的函数或者一个包含多个响应式数据的数组。
    • watchEffect()不需要明确指定数据源,它会自动追踪函数内部使用的响应式数据。
  • 回调函数参数
    • watch()的回调函数接收两个参数:新值和旧值。如果监视多个数据源,新值和旧值分别是一个包含新数据源值的数组和一个包含旧数据源值的数组。
    • watchEffect()的回调函数不接收新值和旧值参数,它只接收一个用于停止副作用的清理函数作为可选参数。
  • 是否立即执行
    • watch()在创建时不会立即执行回调函数,除非设置immediate: true
    • watchEffect()在创建时会立即执行传入的函数。

watchPostEffect()​

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

watchSyncEffect()​

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

标签:函数,person,watch,value,watchEffect,API,监视,newValue
From: https://blog.csdn.net/fishmemory7sec/article/details/141334093

相关文章

  • LLM的API KEY获取和测试
    openaiopenai国内不能访问,只能通过第三方渠道访问,以F2API为例获取APIKEY地址:https://f2api.com/login测试APIKEY是否可用fromlangchain_openaiimportChatOpenAIllm=ChatOpenAI(openai_api_base="https://api.f2gpt.com",openai_api_key="******")res=llm.i......
  • 【实用】【一眼就会】【直接可用】文件上传 附件上传 前后端分离 分布式 多文件上传
    思路:1、先保存主要信息,存到数据库。2、查询这条数据的id、uid3、上传附件功能:根据id、uid、文件。请求:附件API接口。4、后端接口中:先判断登录状态,5、创建对应的文件夹并存入文件,文件夹名以id名命名。6、把附件名重命名,以uuid命名。7、把所有的文件路径放到集合里,传入id......
  • apisix部署
    apisix部署1、部署etcd,传送阵:单节点部署、集群部署。 2、使用rpm部署apisix,官网还有docker、helm、deb、源码等方式部署,详情参看官网:https://apisix.apache.org/docs/apisix/installation-guide/。yuminstall-yyum-utilsyuminstall-yhttps://repos.apiseven.com/pack......
  • iPaaS丨API低代码平台适用的业务场景
    API低代码开发平台在数字化转型加速的当下,API低代码开发平台作为技术创新的前沿阵地,正逐步成为企业构建高效、灵活IT架构的关键支撑。该平台不仅继承了微服务架构的诸多优点,如高内聚、低耦合,还深度融合了低代码开发理念,为开发团队提供了前所未有的便捷与高效。平台通过数据模型......
  • 智能小程序 Ray 开发面板 SDK —— 多语言 API 汇总
    APIAPI示例中的多语言数据源均来自于下方多语言对象exportdefault{en:{dsc_edit:'Edit',//Basicmultilanguagewithdsc_startandnameitsemanticallydsc_hour:'Hour',dsc_minute:'Minute',dsc_countdown_on:'Turnon......
  • API数据在跨境电商发挥什么作用?
    跨境电商与API数据密切相关,API数据在跨境电商的各个环节都发挥着重要作用,具体关系如下:数据获取与同步:API作为数据交换的接口,使跨境电商系统能够实时获取不同电商平台的商品数据,如价格、库存、描述、图片等详细信息,并保持数据的同步更新。这样商家可以在自己的跨境电商......
  • vue3 响应式 API:computed()
    介绍基本概念:computed()接收一个getter函数或者一个包含getter和setter函数的对象作为参数,并返回一个基于原始响应式数据计算得到的新的响应式数据对象。计算属性的值会根据其依赖的响应式数据自动更新,当依赖的数据发生变化时,计算属性的值也会自动重新计算。返......
  • 【收藏】Arcgis api4.x renderer根据字段值渲染,唯一值渲染和分级渲染
    1.根据字段值渲染(唯一值渲染)  定义唯一值渲染器中每个唯一值的样式,以根据数据字段的不同值为要素指定不同的符号,从而实现基于分类数据的图形化表示。 constrenderer={type:"unique-value",field:"type",defaultSymbol:{type:"simple-marke......
  • AI 创业及变现新思路:零门槛 AI 绘图,定制 ComfyUI Serverless API 应用
    作者:鸥弋、筱姜2023年下半年,ComfyUI以其快速、流畅的图像生成能力,结合多样的自定义节点,迅速在创作者中流行起来。ComfyUI的亮点就是能够批量化生成图像,一键加载大量工作流,让用户可以轻松实现人像生成、背景替换、风格迁移和图像动画化等功能。越来越多的企业及个人开发者希望......
  • 一款专为IntelliJ IDEA用户设计的插件,极大简化Spring项目中的API调试过程,功能强大(附源
    前言在软件开发过程中,尤其是SpringMVC(Boot)项目中,API调试调用是一项常见但繁琐的任务。现有的开发工具虽然提供了一些支持,但往往存在效率不高、操作复杂等问题。为了处理这些痛点,提升开发效率,一款新的工具应运而生。介绍CoolRequest是一款专为IntelliJIDEA用户设计的插......