写点什么

VUE3 中 watch 与 watchEffect —— 全网最详细系列

  • 2022 年 9 月 16 日
    河南
  • 本文字数:3729 字

    阅读完需:约 12 分钟

VUE3中watch与watchEffect —— 全网最详细系列

在讲 watch 之前,我们先来看看 watchEffect

一、 watchEffect

立即执行传入的一个函数,同时==响应式追踪其依赖==,并在其依赖变更时重新运行该函数。


watchEffect的一些特点:


  • 不需要手动传入依赖(不用指定监听对象)

  • 无法获取原始值,只能获取更新后的值

  • 立即执行(在onMounted前调用)

  • 一些异步操作放里面更加的合适


watchEffect第一个参数是一个箭头函数(是一个副作用函数),里面用来获取侦听到的新值以及用来进行其它操作(比如清除副作用等)


const count = ref(0)
watchEffect(() =>{ console.log(count.value) // -> logs 0})
setTimeout(() => { count.value++ // -> logs 1}, 1000)
复制代码

1. 停止监听

watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。


但在某些场景下我们需要手动的去停止监听,则我们可使用如下写法:


const stop = watchEffect(() => {  /* ... */})// 停止监听stop()
复制代码

2. 更改监听时机(更改副作用刷新时机)

先来看看副作用的刷新时机也就是监听时机:


const count = ref(0)watchEffect(() => {   console.log(count.value)})
复制代码


在这个例子中:


  • count 会在初始运行时同步打印出来

  • 更改 count 时,将在组件更新执行副作用。


更改watchEffect的监听时机可以对其设置flush的值


flush取值:

  • pre (默认)

  • post (在组件更新后触发,这样你就可以访问更新的 DOM。这也将推迟副作用的初始运行,直到组件的首次渲染完成。)

  • sync (与 watch 一样使其为每个更改都强制触发侦听器,然而,这是低效的,应该很少需要)


例如:


const state = reactive({  id: 1,  attributes: {    name: '',  }})
复制代码


watchEffect(() => {  console.log('名字被修改了', state.attributes.name);})state.attributes.name = 'Ailjx' /** * 打印结果(因为在组件更新前侦听就执行了一次,所以会打印两次) * 名字被修改了  * 名字被修改了 Ailjx */
复制代码


watchEffect(() => {  console.log('名字被修改了', state.attributes.name);}, {  flush: 'post'})state.attributes.name = 'Ailjx' /** * 打印结果(组件更新后触发,所以只会打印一次) * 名字被修改了 Ailjx */
复制代码

3. 清除副作用

在说清除副作用前先来了解一下什么是副作用:副作用是指一个函数在返回值时还干了其它事(该函数称之为副作用函数)如:


  • 修改一个变量

  • 设置一个对象的成员

  • 终端打印日志

  • 修改 DOM

  • 时间监听或订阅

  • 等等


有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。watchEffect可在第一个参数(函数)里传入一个参数onInvalidate 用来清除副作用


onInvalidate 执行时间:


  • 副作用即将重新执行时(监听对象改变时)

  • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)


const id = ref(0); watchEffect((onInvalidate) => {  //假设asyncOperation函数为一个异步请求操作,是副作用函数,这里只是用来演示,不用纠结这个函数具体是干嘛的  const token = asyncOperation(id.value);  onInvalidate(() => {    /**   * 如果id已更改,或watchEffect停止运行时执行:   * token.cancel();   * cancel()的作用是取消请求,这里用与取消token的异步请求来取消异步请求操作产生的副作用   */    token.cancel();  });});
复制代码

4. 侦听器调试

onTrackonTrigger 选项可用于调试侦听器的行为。


  • onTrack 将在响应式 propertyref 作为依赖项被追踪时被调用。

  • onTrigger 将在依赖项变更导致副作用被触发时被调用。


这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。建议在以下回调中编写 debugger 语句来检查依赖关系:


watchEffect(  () => {    /* 副作用 */  },  {    onTrigger(e) {      debugger    }  })
复制代码


onTrackonTrigger 只能在开发模式下工作。

一、watch

1. 监听单一源

// 侦听 reactiveconst state = reactive({ count: 0 })watch(() => state.count,  (count, prevCount) => {    /* count为新值,prevCount为旧值 */  })
// 直接侦听refconst count = ref(0)watch(count, (count, prevCount) => { /* ... */})
复制代码

2. 监听多个源

const firstName = ref('')const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => { console.log(newValues, prevValues)})
firstName.value = 'John' // logs: ["John", ""] ["", ""]lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""]
复制代码


如果你在同一个函数里同时改变这些被侦听的来源,并且该函数不是声明后不立即调用,则侦听器仍只会执行一次:==多个同步更改只会触发一次侦听器。==


<button @click="changeValues">改变</button>
复制代码


  const firstName = ref('')  const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => { console.log(newValues, prevValues) })
const changeValues = () => { firstName.value = 'John' lastName.value = 'Smith' // 只打印一个 ["John", "Smith"] ["", ""] (说明监听只触发了一次) }
复制代码


但如果你在同一个函数里同时改变这些被侦听的来源,并且这个函数在声明后就被调用,则侦听器会执行多次:


例如上述代码改为:


  const firstName = ref('')  const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => { console.log(newValues, prevValues) })
const changeValues = () => { firstName.value = 'John' lastName.value = 'Smith' //打印两次,则侦听器执行了两次 // ["John", ""] ["", ""] // ["John", "Smith"] ["John", ""] } //在声明后立即调用 changeValues()
复制代码


如果你想要让侦听器始终能够监听多次(为每个更改都强制触发侦听器),可以设置flush: 'sync'(不推荐):


<button @click="changeValues">改变</button>
复制代码


const firstName = ref('')const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => { console.log('改变的-->', newValues, prevValues)}, { flush: 'sync'})
const changeValues = () => { firstName.value = 'John' lastName.value = 'Smith' //打印两次,则侦听器执行了两次 // ["John", ""] ["", ""] // ["John", "Smith"] ["John", ""]}
复制代码


或者,可以用 nextTick 等待侦听器在下一步改变之前运行。例如:


const changeValues = async () => {  firstName.value = 'John' // 打印 ["John", ""] ["", ""]  await nextTick()  lastName.value = 'Smith' // 打印 ["John", "Smith"] ["John", ""]}
复制代码

3. 侦听响应式对象

使用侦听器来比较一个数组或对象的值,这些值是响应式的,要求它有一个由值构成的副本。


const numbers = reactive([1, 2, 3, 4])//...numbers解构numberswatch(() => [...numbers],  (numbers, prevNumbers) => {    console.log(numbers, prevNumbers)  })
numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
复制代码

4. 深度监听

当监听源为多重嵌套时,想要监听到该源的深层部分则需要设置deep: true 例如在监听state 时需要同时监听它里的attributes.name


const state = reactive({   id: 1,  attributes: {     name: '',  }})
复制代码


watch(  () => state,  (state, prevState) => {    console.log('deep', state.attributes.name, prevState.attributes.name)  },  { deep: true })//根据打印结果发现打印出的新旧值都为改变后的值state.attributes.name = 'Alex' // 日志: "deep" "Alex" "Alex"
复制代码


但这样使用后会发现watch侦听打印的新旧值都是修改后的值,为了完全侦听深度嵌套的对象和数组,可能需要对值进行==深拷贝==:


// 使用JSON.parse(JSON.stringify(state))对state进行深拷贝watch(  () => JSON.parse(JSON.stringify(state)),  (state, prevState) => {    console.log('deep', state.attributes.name, prevState.attributes.name)  },  { deep: true })
state.attributes.name = 'Alex' // 日志: "deep" "Alex" ""
复制代码

5. 立即执行

如果想要 watch 函数立即执行,这可设置immediate:true


watch(  () => JSON.parse(JSON.stringify(state)),  (state, prevState) => {    console.log('deep', '新值:' + state.attributes.name, '旧值:' + prevState.attributes.name)  },  {    //立即执行    immediate: true,    deep: true  },
)
复制代码


watchwatchEffect共享停止侦听清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机侦听器调试行为。例如:


const stop = watch(  () => JSON.parse(JSON.stringify(state)),  (state, prevState, onInvalidate) => {    console.log('deep', '新值:' + state.attributes.name, '旧值:' + prevState.attributes.name)    onInvalidate(() => {      /* 清除副作用代码 */    })  },  {    immediate: true,    deep: true,    //侦听器调试    onTrigger(e) {      debugger    }  },
)stop()
复制代码


发布于: 刚刚阅读数: 3
用户头像

前端之行,任重道远! 2022.08.25 加入

本科大三学生、CSDN前端领域新星创作者、华为云享专家、第十三届蓝桥杯国赛三等奖获得者

评论

发布
暂无评论
VUE3中watch与watchEffect —— 全网最详细系列_前端_海底烧烤店ai_InfoQ写作社区