侦听器watch

调用ref、reactive、computed函数能返回一个响应性对象,而调用watch只是创建一个看不见的侦听器。watch在后台侦听参数1内的响应式对象,当响应式对象状态发生变化时,会按参数2的回调函数代码执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。

一、书写规范:

watch ( 监听源, 回调函数 ,配置项)

监听源

监听响应式对象: 监听源必须是响应式对象,非响应式对象不能监听,监听对象默认是深层监听,子孙属性都能监听到。
监听非底层属性: 可以直接侦听非底层属性,默认是深层监听。
监听底层属性:
不能直接侦听底层属性,需将其放在getter 函数里。

  • getter函数: 有return能返回值的函数名为getter函数。如下:实名式 function er(){return a.b},匿名式 () => {return a.b},匿名简写式() =>a.b。
  • getter多值 可以返回多个值,数组型值用[a.b,c.e]中括号,对象型用{}括号,当是对象时外面还需套个小括号以与函数的大括号区别开,如({f:'io',r:'yu'})
  • 深层监听: getter函数默认是表层监听,如果getter函数不是底层属性而是非底层属性,其子孙属性将不能监听到,需要通过配置项deep:true来开启深层监听。

监听ref: ref对象不具DOM响应性,value属性才具有响应性,因此不能监听ref对象,只能监听value属性,当value属性为底层属性时,监听value底层属性书写时不需要() =>a.value,而是直接“a”,省略getter和.value。当value属性非底层属性时,书写不能省略.value。如下,x不用加value,y要加value。

var x = ref(0)
var y = ref({ww:0,yy:2})
watch([x,y.value], (ne,ol) => {})

多个监听源: 可以监听多个源,但多个侦听源必须以数组的形式用[]方括号括起。

回调函数

1、回调函数​的概念
调用函数时的实参,类型如果是个函数,这个函数形实参就叫回调函数。如下,调用函数yu时的函数形实参diao(x.value)与diao都是回调函数。

<template>
  <button @click="gb">我来改变</button>
</template>
<script setup>
import { ref} from 'vue'
var x = ref(0)
function yu(a,b){a;b(x.value)} //本体函数
function diao(c){console.log(c)} //参数函数
yu(diao(x.value),diao)//调用本体函数
</script>

引用与调用
在本体函数使用函数式形参时,会采用“引用”和“调用”两种方式使用形参,如下:function yu(a,b){a;b()},a是引用b()是调用。

  • 引用方式: 本体函数“引用”方式使用形参时,回调函数必须是调用式,即回调函数是调用函数,实参在回调函数的()内。
  • 调用方式: 本体函数“调用”方式使用形参时,回调函数必须是引用式,即回调函数是引用函数,实参在本体函数的调用()内放。

通常本体函数采用调用方式使用形参,以保证函数调用在本体函数内发生,调用时的参数封装在本体函数内,以保证结果可控。
引用式回调函数
因为本体函数通常采用调用式,以致回调函数多是引用式,引用式回调函数有三种写法。

  • 署名: 如:yu(function diao(b){})。
  • 匿名: yu((b)=>{})。
  • 外部函数: 使用外部函数,如:yu(diao)。

2、watch 的回调函数书写规范
watch的回调函数是引用式的, ()内是形参
(新值形参, 原值形参,注销函数形参) =>{执行副作用指令;注销函数(二级回调函数)}。
注销函数的使用详见“watchEffect”。
侦听源是数组时

  • 形参与侦听源一一对应,形参也需写在[]内,如:([新值形参组],[旧值形参组]) =>{执行副作用指令}。
  • 形参不与侦听源对应,形参以数组名对应侦听源组,如:(新值组名,旧值组名) =>{执行副作用指令}。在副作用指令中可以通过“新值组名[索引值]”的形式与侦听源的数组一一对应来使用。如下:

    <template>
    <span>{{mabi}}</span><br>
    <button @click="pp">侦听源一</button>
    </template>
    <script setup>
    import { reactive,watch, ref} from 'vue' 
    const wocao = reactive({name:0,na:2})
    const mabi = ref(3)
    watch([wocao,mabi],([www,eee],[ww,ee])=>console.log(www,eee))
    watch([wocao,mabi],(www,eee)=>console.log(www[0],www[1]))
    const pp=function(){wocao.na++}
    </script>

3、源与新旧形参的关系

  • 源与形参是赋值关系
    当源是底层属性时,形参是基本形变量,采用栈内存固定地址,与源不相互响应。当源是非底层属性时,形参是引用形变量,与源使用同一指针,与源相互响应,改变形参会同时改变非底层属性源
  • 回调函数对源赋值再运行
    回调函数内对源赋值会导致监听再运行一次回调函数。如是自增减还会出现递归溢出错误。如下:

    <template>
    <span>{{wocao}}</span><br>
    <button @click="pp">侦听源一</button>
    </template>
    <script setup>
    import { reactive,watch, ref} from 'vue' 
    const wocao = reactive({name:0,na:2})
    watch(wocao,(www,ww)=>{www.name=3,console.log('我会运行几次?')})
    const pp=function(){wocao.na=5}
    </script>
  • 无法获得旧值
    源为非底层属性时,无法获得旧值,新旧形参都是新值。
    可通过扩展运算符“...”解开非底层属性,转换为子属性合集。
    注意不能直接侦听底层属性,需将其放在getter 函数里。如下:数组用的是[]对象用的是{},且对象的getter函数不能简写。

    <template>
    <span>{{wocao}}</span><br>
    <button @click="pp">侦听非底层数组</button>
    <button @click="po">侦听非底层对象</button>
    </template>
    <script setup>
    import { reactive,watch, ref} from 'vue' 
    const wocao = reactive({name:0,na:2})
    const woc = reactive([0,2])
    watch(()=>[...woc],(www,ww)=>{console.log('我是数组,',www,ww)})
    watch(()=>{return {...wocao}},(www,ww)=>{console.log('我是对象,',www,ww)})
    const pp=function(){woc[0]++}
    const po=function(){wocao.name++}
    </script>

4、回调执行时机
回调总是在一个作用域的最后执行,因此当同一域有多个响应变量发出告知,并不是每次告知都会执行回调,只会在最后一次得到告知后才会执行回调。

配置项

配置项须以对象 的形式写在{}里
1、deep: true 控制对象型的侦听源的 深层侦听。
2、immediate: true 初始时就立即执行一次,而不必等待侦听源改变时才执行。
3、flush: 'post' DOM 更新时机(VUE为了减少响应次数,会让所有的DOM更新在一段代码的最后执行),flush: 'post'强制DOM更新后执行。
4、once: true 回调只在源变化时触发一次,以后源变化不再触发。

二、停止侦听

watch函数会返回一个停止侦听函数,调用他会停止侦听,如下:

<template>
  <span>{{wocao}}</span><br>
  <button @click="pp">侦听源一</button>
</template>
  <script setup>
  import { reactive,watch, ref} from 'vue' 
  import { cloneDeep } from 'lodash'
  const wocao = reactive({name:0,na:2})
  const woca=watch(()=> cloneDeep(wocao),(www,ww)=>{console.log(www,ww)},{deep: true})
  const pp=function(){wocao.na++;if(wocao.na>5){woca()}}
</script>

三、watch​与computed

watch​与computed都依赖于侦听源,触发方式相同,作用相似。

1、watch 实现 computed功能

调用computed函数返回一个对象,return返回计算值给对象的value;watch函数不返回对象,无法return计算值给对象,需要另外定义一个响应变量用于存取计算值。如下:

<template>
  <span>{{mabi}}</span><br>
  <button @click="pp">侦听源一</button>
</template>
  <script setup>
  import { reactive,computed,watch,ref } from 'vue' 
  const wocao = reactive({name:0,na:2})
  const mabi = ref(wocao.name)
  watch(wocao,(www,eee)=>{console.log('计算一次');mabi.value=wocao.name+1})
  const pp=function(){wocao.na++,wocao.name=mabi.value}
</script>

2、computed 实现watch 功能

watch 有副作用指令集,里面可以放多个指令,很方便通过指令改变多个值;computed 的 return后面只能放一条指令,其它指令需放在return之前。

3、watch 与 computed 最佳使用场景

computed的return 能很方便的返回一个值,适合于一个数据受多个数据影响的场景。
watch的副作用指令集能很方便的放多个指令,适合于一个数据影响多条数据的场景。

4、watch 与 computed的曲别

  • watch 与 computed 侦听源
    computed 指令必须含侦听源,书写限制多,watch侦听源与指令分开,指令可以不带侦听源,书写更自由。
  • watch 与 computed 侦听时机
    computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载做监听,需添加immediate:true
  • watch 与 computed 缓存
    computed支持缓存,相依赖的数据发生改变才会重新计算;watch不支持缓存,只要监听的数据变化就会触发相应操作
  • watch 与 computed 异步
    computed不支持异步,当computed内有异步操作时是无法监听数据变化;watch支持异步操作。

即时侦听器watchEffect

watchEffect是watch的简洁版,watchEffect能做的watch都能做。
watchEffect与watch最大的区别是侦听对象的位置不同
watch 将侦听对象放在侦听源内,不侦听回调函数中的对象;watchEffect没有侦听源,而是侦听回调函数中的对象。

一、书写规范:

watchEffect(回调函数 ,配置项)。注意“E”为大写

回调函数

1、二级回调函数内部原理
能使用二级回调函数,是因为本体函数调用一级回调函数时创建了本体回调函数,这个本体回调函数可接收调用体的二级回调函数。如下:

<template>
  <h1>二级回调函数的内部工作原理</h1>
</template>
<script setup>
function watchEff(a){console.log('我是本体必作用副作用集,形级接收一级回调函数');a((d)=>{
  console.log('我是本体回调函数作用集,传给调用体当形参,我的形参接收二级回调函数')
  d()})}//本体
watchEff((b)=>{console.log('我是一级回调函数作用集,传给本体当形参,我的形参接收本体回调函数')
b(()=>{console.log('我是二级回调函数,传给本体回调函数当形参')})})//调用体
</script>

2、watchEffect的回调函数书写规范
(注销函数形参) =>{执行副作用指令集;注销函数(二级回调函数)}
3、注销函数
调用此函数能方便的管理副作用的生命周期。注销函数本体如下:

(fn) => {
    cleanup = effect2.onStop = () => {
      callWithErrorHandling(fn, instance, 4);
    };
  }

本体是个回调函数,这个本体回调函数的形参是个函数类形,因此调用注销函数时的实参必须是回调函数,调用写法如下:
注销函数(() => {初始或销毁作用集})

  • 注销函数触发条件
    当以下情况发生时,这个形参回调会被触发:

    • 副作用即将重新执行时(即依赖的值改变)
    • 侦听器被停止 (通过显示调用返回值停止侦听,或组件被卸载时隐式调用了停止侦听)
  • 注销函数最佳使用场景 由触发条件可知,注销函数适用的最佳使用场景是:

    • 在副作用即将重新执行时,如果有要重置的选项,可以通过注销函数重新初使化以防原来的副作用对即将执行的副作用污染。
    • 侦听器被停止时,如果有要消毁的选项,可以在onInvalidate里消毁以防污染其它组件。

配置项 flush:

flush控制侦听执行副作用时机,有三个值。
1、pre在组件内存渲染阶段之前或更新之前执行。默认值为'pre'。
2、post在组件更新后运行,可使用watchPostEffect函数代替。
3、sync强制效果始终同步触发,可用watchSyncEffect代替。然而,这是低效的,应该很少需要。

二、停止侦听

watchEffect函数会返回一个停止侦听函数,调用他会停止侦听。
同步创建的侦听器在组件卸载时自动停止,异步创建的侦听器在组件卸载时不会自动停止。如下方这个例子:
子组件

<template>
  <span>我是子组件</span><br>
</template>
<script setup>
import { watch ,ref} from 'vue'
const tt=ref(0)
watch(tt,() =>{console.log('我是与组件同步的watch')})
setTimeout(() => {
  watch(tt,() =>{console.log('我是与组件不同步的watch')})
}, 100)
setInterval(() => {
 if(tt.value<10){tt.value++}
}, 1000)
</script>

父组件

<template>
  <HelloWor  v-if="iii"/><br>
  <button @click="ee">点我卸载组件,异步侦听器不会自动停止</button>
</template>
<script setup>
import HelloWor from './He.vue'
import {ref} from 'vue'
const iii=ref(true)
function ee(){iii.value=false}
</script>

异步回调创建的侦听器,必须手动停止它,以防组件卸载后还在运行,导致内存泄漏。
详见组件的生命周期详解

三、watch​与watchEffect

2、watchEffect 不需在配置项里使用immediate: true,默认始化时就执行一次,不必等到侦听源改变时才执行。
3、watchEffect 无法象 watch一样返回新旧值的形参,监听源的值即为新值。


百分之一百零八
15 声望3 粉丝