汕头烟囱耐高温油漆行业标准引领未来
一、一个故事讲清楚想象你有一个“购物车”。里面有商品数量和单价。总价是“算”出来的数量或单价变了总价自动跟着变。这就是computed的活儿。库存告警当数量超过库存时你要“做”点事比如弹个提醒。这就是watch的活儿。用人话总结computed给我一个公式我给你一个结果。我还会缓存依赖不变我就不重新算。watch你给我盯住一个数据它一变化你就执行我交代的任务。下面我们一个个来。二、computed计算属性2.1 最简单的例子vuetemplate div p单价{{ price }} 元/p p数量input v-model.numbercount typenumber //p !-- 直接用计算属性 total就像用普通数据一样 -- p总价{{ total }} 元/p /div /template script setup import { ref, computed } from vue // 单价固定为 10 const price ref(10) // 数量初始为 1 const count ref(1) // computed 接收一个函数返回一个“计算属性” // total 的值会自动根据 price 和 count 的变化重新计算 const total computed(() { // 这个函数里用到的响应式数据变了total 就会自动更新 return price.value * count.value }) // 现在 total 就是一个响应式的只读引用模板里直接用 {{ total }} /script关键点computed返回的是一个ref 对象在模板里自动解包直接用。它自动追踪依赖price、count任一变了都会重新计算。缓存机制只要依赖没变多次访问total.value不会重复执行函数直接返回上次算好的值。2.2 为什么用 computed 而不是直接在模板里写表达式你可以在模板里写{{ price * count }}但如果有复杂逻辑模板会变得又乱又难维护。而且 computed 有缓存性能更好。2.3 计算属性也可以有 getter 和 setter有时候你需要既能读又能写的计算属性比如双向绑定。vuetemplate div p姓input v-modelfirstName //p p名input v-modellastName //p !-- fullName 是计算属性可以用 v-model 双向绑定 -- p全名input v-modelfullName //p /div /template script setup import { ref, computed } from vue const firstName ref(张) const lastName ref(三) // 带 get 和 set 的计算属性 const fullName computed({ // get读取 fullName.value 时执行 get() { return firstName.value lastName.value }, // set给 fullName.value 赋值时执行 set(newValue) { // 假设把全名按空格拆开分别赋值 const names newValue.split( ) firstName.value names[0] || lastName.value names[1] || } }) // 现在你在“全名”输入框输入“李 四”姓和名会自动分开填好 /script三、watch侦听器watch 就是专门来“盯梢”的某个数据变了你就执行一个回调在回调里做你想做的事。3.1 基础用法监听一个 refvuetemplate div p搜索关键词input v-modelkeyword //p p v-ifresult搜索结果{{ result }}/p /div /template script setup import { ref, watch } from vue const keyword ref() const result ref() // watch(要监听的数据, 回调函数) watch(keyword, (newValue, oldValue) { // newValue变化后的值 // oldValue变化前的值 console.log(关键词从 ${oldValue} 变成了 ${newValue}) // 模拟搜索请求 if (newValue.trim()) { // 实际项目里这里会调用 API result.value 关于${newValue}的搜索结果... } else { result.value } }) /script关键点第一个参数是要监听的数据源ref 对象、reactive 属性、getter 函数或它们的数组。第二个参数是回调数据变化时执行参数为(newVal, oldVal)。watch 是懒执行的第一次不执行只有数据变化才执行。如果需要初始就执行用immediate: true。3.2 监听多个数据vuescript setup import { ref, watch } from vue const firstName ref() const lastName ref() // 监听多个数据源参数变成数组 watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) { // 参数也是数组一一对应 console.log(姓名从 ${oldFirst} ${oldLast} 变成了 ${newFirst} ${newLast}) }) /script3.3 深度监听当监听的是一个对象或数组时默认情况下只有整体被替换才会触发 watch对象内部属性变化不会触发。这时需要deep: true。vuescript setup import { reactive, watch } from vue const user reactive({ name: 小明, age: 20 }) // 深度监听整个对象 watch(user, (newVal, oldVal) { // 由于是深度监听对象内部属性变了会触发 console.log(user 变了, newVal) }, { deep: true }) // 第三个参数是一个配置对象deep: true 表示深度监听 // 注意此时 newVal 和 oldVal 是同一个引用完全一样 // 因为对象是引用类型Vue 不会保存旧值的快照 /script3.4 立即执行vuescript setup import { ref, watch } from vue const keyword ref(Vue) // immediate: true 会让回调在 watch 创建时立即执行一次 watch(keyword, (newVal) { console.log(立即执行搜索, newVal) }, { immediate: true }) // 这样一进页面就会用初始值 Vue 去搜索一次 /script3.5 用 getter 函数精准监听某个属性监听 reactive 对象的某个属性不能直接写watch(user.name, ...)而要写成 getter 函数vuescript setup import { reactive, watch } from vue const user reactive({ name: 小明, age: 20 }) // 正确方式用 () user.name 函数返回要监听的属性 watch(() user.name, (newName) { console.log(名字变成了, newName) }) // 错误方式watch(user.name, ...) 这样不会生效 /script四、watchEffect更自动化的 watchVue3 新增watchEffect是更简单的一种侦听方式。它不用你手动指定依赖它会自动追踪你在函数里用了哪些响应式数据只要任何一个变了就重新执行这个函数。vuetemplate div p姓input v-modelfirstName //p p名input v-modellastName //p p全名{{ fullName }}/p /div /template script setup import { ref, watchEffect } from vue const firstName ref(张) const lastName ref(三) const fullName ref() // watchEffect 接收一个函数立即执行一次同时自动追踪依赖 watchEffect(() { // 在这个函数里用到了 firstName 和 lastName // 它们两个任何一个变了这个函数都会重新执行 fullName.value firstName.value lastName.value console.log(全名更新为, fullName.value) }) /scriptwatchEffect vs watchwatchEffect立即执行watch默认懒执行除非immediate: true。watchEffect自动收集依赖不用手动指定watch要明确指定监听谁。watchEffect拿不到旧值watch可以拿到新旧值对比。如果你的逻辑里有条件判断依赖不同的数据watchEffect可能更灵活。五、computed vs watch vs watchEffect 该选谁场景推荐原因根据已有数据计算一个新值并显示在模板里computed有缓存性能好语义清晰数据变化时需要执行异步操作发请求、定时器watch可以拿到旧值适合做副作用数据变化时要更新多个关联数据但不需要旧值watchEffect自动追踪依赖代码更简洁需要深度监听一个对象内部的变化watch deep: truewatchEffect 也可以但最好明确配置六、实战案例商品筛选器一个完整的例子包含 computed 和 watch 协作。vuetemplate div h2商品筛选/h2 !-- 搜索框 -- input v-modelsearchText placeholder输入商品名搜索 / !-- 价格区间 -- p最高价格input v-model.numbermaxPrice typenumber //p !-- 展示过滤后的商品 -- ul li v-foritem in filteredProducts :keyitem.id {{ item.name }} - ¥{{ item.price }} /li /ul !-- 搜索结果数量 -- p共 {{ filteredProducts.length }} 件商品/p /div /template script setup import { ref, computed, watch } from vue // 原始商品列表实际项目可能从 API 获取 const products ref([ { id: 1, name: 苹果, price: 5 }, { id: 2, name: 香蕉, price: 3 }, { id: 3, name: 橘子, price: 4 }, { id: 4, name: 西瓜, price: 10 }, { id: 5, name: 葡萄, price: 15 } ]) // 搜索关键词 const searchText ref() // 最高价格 const maxPrice ref(999) // --- computed根据搜索关键词和最高价格过滤商品 --- const filteredProducts computed(() { // 先根据名字模糊搜索 let result products.value.filter(item item.name.includes(searchText.value) ) // 再根据最高价格过滤 result result.filter(item item.price maxPrice.value) return result }) // --- watch监听搜索结果数量如果为0就提示 --- watch( () filteredProducts.value.length, // 用 getter 监听计算属性的值 (newCount) { if (newCount 0) { console.log(没有找到匹配的商品) // 实际可以弹个 toast 提示 } } ) /script七、练习题选择题以下哪个特性是 computed 具备而 watch 不具备的A. 执行异步操作B. 自动收集依赖C. 计算结果会被缓存D. 可以拿到旧值当需要监听一个 ref 对象内部属性的变化应该使用A. watch(ref)B. watch(ref, { deep: true })C. watchEffect(() ref.value)D. B 和 C 都可以关于 watchEffect以下描述正确的是A. 默认不立即执行B. 需要手动指定监听的数据C. 自动追踪函数内部用到的响应式依赖D. 可以获取到旧值判断题在 computed 的 getter 函数里可以修改其他响应式数据。 watch 默认深度监听对象内部属性的变化。 watchEffect 会在创建时立即执行一次。 编程题实现一个“全选”功能有一组复选框苹果、香蕉、橘子一个“全选”复选框使用 computed 计算是否全选选中项数 总项数当“全选”复选框变化时用 watch 同步所有子项的选中状态使用 watchEffect 实现监听用户输入的关键词当长度 ≥ 2 时模拟异步搜索用 setTimeout把搜索结果显示在页面上。注意处理防抖可简单实现当输入停止 500ms 后才真正搜索。八、答案C。computed 有缓存特性watch 不具备。D。深度监听对象内部属性可以用watch(ref, { deep: true })也可以用watchEffect在函数内部访问 ref 的属性自动追踪。C。watchEffect 自动追踪依赖立即执行不能获取旧值。错误。computed 的 getter 应该是纯函数不能包含副作用如修改其他数据否则可能导致无限循环或预期外的行为。错误。watch 默认是浅层监听只有整个 ref 或 reactive 对象的引用被替换才会触发。要监听内部属性需加deep: true。正确。watchEffect 在创建时会立即执行一次回调之后依赖变化时再次执行。参考实现vuetemplate div labelinput typecheckbox v-modelallChecked / 全选/label br / label v-forfruit in fruits :keyfruit input typecheckbox :valuefruit v-modelselected / {{ fruit }} /label p已选{{ selected }}/p /div /template script setup import { ref, computed, watch } from vue const fruits [苹果, 香蕉, 橘子] const selected ref([]) // 存放选中的水果名 // 计算是否全选选中的数量等于总数量 const allChecked computed({ get() { return selected.value.length fruits.length }, set(value) { // 当全选框被点击时value 是 true 或 false if (value) { selected.value [...fruits] // 全选复制整个数组 } else { selected.value [] // 取消全选 } } }) // 用 watch 同步当手动勾选/取消子项导致全选状态变化时 // 其实上面用 computed 的 setter 已经处理了全选框点击 // 这里可以不需要 watch 了但我们可以用 watch 监听 selected 变化做点其他事 watch(selected, (newVal) { console.log(当前选中, newVal) }, { deep: true }) /script参考实现vuetemplate div input v-modelkeyword placeholder输入至少2个字搜索 / p v-ifloading搜索中.../p ul v-else li v-foritem in results :keyitem{{ item }}/li /ul /div /template script setup import { ref, watchEffect } from vue const keyword ref() const results ref([]) const loading ref(false) let timer null // 定时器 ID用于防抖 // watchEffect 自动追踪 keyword一变化就执行 watchEffect(() { // 清除上一次的定时器实现防抖只有停止输入 500ms 后才搜索 clearTimeout(timer) if (keyword.value.length 2) { results.value [] loading.value false return } loading.value true // 模拟异步搜索 timer setTimeout(() { // 假数据 const allData [苹果, 香蕉, 橘子, 西瓜, 葡萄] results.value allData.filter(item item.includes(keyword.value)) loading.value false }, 500) }) /script写在最后computed 和 watch 是 Vue 里每天都打交道的东西。刚开始你可能只会用 computed慢慢地遇到需要在数据变化时做点额外操作比如发请求、存 localStorage就会自然地用上 watch 和 watchEffect。多写几个需求这三兄弟的分工就刻在肌肉记忆里了。有问题评论区说我挨个回。下篇咱们讲模板引用和自定义指令