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

简介: 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. 监听单一源

// 侦听 reactive
const state = reactive({ count: 0 })
watch(() => state.count,
  (count, prevCount) => {
    /* count为新值,prevCount为旧值 */
  }
)

// 直接侦听ref
const 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解构numbers
watch(() => [...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()
相关文章
|
28天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
128 64
|
2天前
|
JavaScript API 数据处理
vue3使用pinia中的actions,需要调用接口的话
通过上述步骤,您可以在Vue 3中使用Pinia和actions来管理状态并调用API接口。Pinia的简洁设计使得状态管理和异步操作更加直观和易于维护。无论是安装配置、创建Store还是在组件中使用Store,都能轻松实现高效的状态管理和数据处理。
16 3
|
28天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
32 8
|
27天前
|
存储 JavaScript 数据管理
除了provide/inject,Vue3中还有哪些方式可以避免v-model的循环引用?
需要注意的是,在实际开发中,应根据具体的项目需求和组件结构来选择合适的方式来避免`v-model`的循环引用。同时,要综合考虑代码的可读性、可维护性和性能等因素,以确保系统的稳定和高效运行。
30 1
|
27天前
|
JavaScript
Vue3中使用provide/inject来避免v-model的循环引用
`provide`和`inject`是 Vue 3 中非常有用的特性,在处理一些复杂的组件间通信问题时,可以提供一种灵活的解决方案。通过合理使用它们,可以帮助我们更好地避免`v-model`的循环引用问题,提高代码的质量和可维护性。
36 1
|
1月前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
7天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
1月前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
41 1
vue学习第一章
|
1月前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
30 1
|
1月前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
38 1
vue学习第四章