Vue3 学习教程,从入门到精通,Vue3 监听属性(Watchers)语法知识点及案例代码(16)

发布于:2025-07-26 ⋅ 阅读:(9) ⋅ 点赞:(0)

Vue3 监听属性(Watchers)语法知识点及案例代码

在 Vue 3 中,监听属性(Watchers) 允许你响应数据的变化,并在数据变化时执行特定的逻辑。监听器对于处理异步操作或需要在数据变化时执行复杂逻辑的场景非常有用。

一、监听属性的基本语法

1. 使用 watch 选项

在组件选项中,通过 watch 选项来定义监听器。watch 对象中的每个键对应一个要监听的数据源,值是一个回调函数,当数据源变化时回调函数会被调用。

export default {
  data() {
    return {
      count: 0
    };
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`count 从 ${oldVal} 变为 ${newVal}`);
    }
  }
};

2. 使用 watch 函数

除了在选项式 API 中使用 watch 选项外,Vue 3 还提供了组合式 API 中的 watch 函数,用于在 setup 函数中定义监听器。

import { ref, watch } from 'vue';

export default {
  setup() {
    const count = ref(0);
    
    watch(count, (newVal, oldVal) => {
      console.log(`count 从 ${oldVal} 变为 ${newVal}`);
    });
    
    return {
      count
    };
  }
};

3. 监听器的选项

watch 回调函数可以接受多个参数:

  • 新值(newVal):变化后的新值。
  • 旧值(oldVal):变化前的旧值。
  • onInvalidate:用于清除副作用的函数。

此外,监听器还支持一些选项:

  • immediate:布尔值,设置为 true 时,监听器会在初始化时立即执行一次。
  • deep:布尔值,设置为 true 时,监听器会进行深度监听,适用于监听对象或数组内部的变化。
watch(
  source,
  (newVal, oldVal) => {
    // 回调函数
  },
  {
    immediate: true,
    deep: true
  }
);

4. 监听多个数据源

可以通过数组的形式同时监听多个数据源。

watch([() => this.a, () => this.b], ([newA, newB], [oldA, oldB]) => {
  console.log(`a 从 ${oldA} 变为 ${newA}, b 从 ${oldB} 变为 ${newB}`);
});

5. 停止监听器

在某些情况下,你可能需要手动停止监听器。可以使用 watch 返回的函数来停止监听。

const unwatch = watch(count, (newVal, oldVal) => {
  console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});

// 需要停止监听时调用
unwatch();

二、案例代码

下面是一个完整的 Vue 3 组件示例,演示了如何使用监听属性。这个组件包含一个计数器,用户可以点击按钮增加计数,监听器会监测计数的变化并执行相应的逻辑。

1. 使用选项式 API 的示例

<template>
  <div>
    <h2>计数器</h2>
    <p>当前计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <p>监听器输出:</p>
    <ul>
      <li v-for="(log, index) in logs" :key="index">{{ log }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0,
      logs: []
    };
  },
  watch: {
    // 监听 count 的变化
    count(newVal, oldVal) {
      this.logs.push(`count 从 ${oldVal} 变为 ${newVal}`);
      // 示例:每当 count 变化时,执行异步操作
      this.fetchData(newVal);
    }
  },
  methods: {
    increment() {
      this.count++;
    },
    async fetchData(val) {
      // 模拟异步请求
      const data = await new Promise((resolve) => {
        setTimeout(() => {
          resolve(`服务器返回的数据: ${val}`);
        }, 1000);
      });
      this.logs.push(data);
    }
  },
  // 立即执行一次监听器
  mounted() {
    this.$watch(
      () => this.count,
      (newVal, oldVal) => {
        this.logs.push(`[立即监听] count 从 ${oldVal} 变为 ${newVal}`);
      },
      { immediate: true }
    );
  }
};
</script>

<style scoped>
button {
  margin-top: 10px;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  background: #f0f0f0;
  margin: 5px 0;
  padding: 5px;
}
</style>
解释:
  1. 数据定义

    • count:当前的计数。
    • logs:用于存储监听器输出的日志。
  2. 监听器

    • watch 对象中定义了 count 的监听器。当 count 变化时,回调函数会执行,将新旧值记录到 logs 中,并调用 fetchData 方法模拟异步操作。
  3. 方法

    • increment:增加 count 的值。
    • fetchData:模拟一个异步请求,延迟 1 秒后返回服务器数据。
  4. 生命周期钩子

    • mounted:在组件挂载后,使用 $watch 方法定义一个立即执行的监听器,演示 immediate 选项的使用。
  5. 模板

    • 显示当前的计数。
    • 提供一个按钮来增加计数。
    • 显示监听器的输出日志。

2. 使用组合式 API 的示例

<template>
  <div>
    <h2>计数器(组合式 API)</h2>
    <p>当前计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <p>监听器输出:</p>
    <ul>
      <li v-for="(log, index) in logs" :key="index">{{ log }}</li>
    </ul>
  </div>
</template>

<script>
import { ref, watch, onMounted } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const logs = ref([]);

    // 定义监听器
    const stopWatch = watch(count, (newVal, oldVal) => {
      logs.value.push(`count 从 ${oldVal} 变为 ${newVal}`);
      fetchData(newVal);
    }, {
      immediate: false,
      deep: false
    });

    // 方法定义
    const increment = () => {
      count.value++;
    };

    const fetchData = async (val) => {
      const data = await new Promise((resolve) => {
        setTimeout(() => {
          resolve(`服务器返回的数据: ${val}`);
        }, 1000);
      });
      logs.value.push(data);
    };

    // 立即执行一次监听器
    onMounted(() => {
      watch(
        () => count.value,
        (newVal, oldVal) => {
          logs.value.push(`[立即监听] count 从 ${oldVal} 变为 ${newVal}`);
        },
        { immediate: true }
      );
    });

    return {
      count,
      increment,
      logs
    };
  }
};
</script>

<style scoped>
button {
  margin-top: 10px;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  background: #f0f0f0;
  margin: 5px 0;
  padding: 5px;
}
</style>
解释:
  1. 导入

    • ref:用于定义响应式数据。
    • watch:用于定义监听器。
    • onMounted:生命周期钩子,用于在组件挂载后执行代码。
  2. 数据定义

    • count:当前的计数,使用 ref 定义。
    • logs:用于存储监听器输出的日志。
  3. 监听器

    • 使用 watch 函数定义 count 的监听器。当 count 变化时,回调函数会执行,将新旧值记录到 logs 中,并调用 fetchData 方法模拟异步操作。
  4. 方法

    • increment:增加 count 的值。
    • fetchData:模拟一个异步请求,延迟 1 秒后返回服务器数据。
  5. 生命周期钩子

    • onMounted:在组件挂载后,使用 watch 函数定义一个立即执行的监听器,演示 immediate 选项的使用。
  6. 模板

    • 显示当前的计数。
    • 提供一个按钮来增加计数。
    • 显示监听器的输出日志。

三、完整案例:购物车监听

<template>
  <div>
    <h1>购物车</h1>
    <div>
      <button @click="addItem">添加商品</button>
      <button @click="removeItem">移除商品</button>
      <button @click="changeUser">更改用户</button>
    </div>
    
    <div>
      <h3>购物车内容 ({{ cartItems.length }} 件):</h3>
      <ul>
        <li v-for="(item, index) in cartItems" :key="index">
          {{ item.name }} - ¥{{ item.price }} × {{ item.quantity }}
        </li>
      </ul>
    </div>
    
    <div>
      <h3>总价: ¥{{ totalPrice }}</h3>
    </div>
    
    <div>
      <h3>用户信息:</h3>
      <p>姓名: {{ user.name }}</p>
      <p>会员等级: {{ user.level }}</p>
    </div>
    
    <div v-if="discountApplied">
      <p style="color: green;">已应用 {{ discountRate * 100 }}% 会员折扣!</p>
    </div>
    
    <div v-if="cartChanged">
      <p style="color: orange;">购物车内容已更改,请确认!</p>
    </div>
  </div>
</template>

<script>
import { ref, reactive, computed, watch, watchEffect } from 'vue'

export default {
  setup() {
    // 购物车商品
    const cartItems = ref([
      { id: 1, name: '商品A', price: 100, quantity: 1 },
      { id: 2, name: '商品B', price: 200, quantity: 2 }
    ])
    
    // 用户信息
    const user = reactive({
      name: '张三',
      level: 'gold', // silver, gold, platinum
      discountRates: {
        silver: 0.95,
        gold: 0.9,
        platinum: 0.85
      }
    })
    
    // 计算属性
    const totalPrice = computed(() => {
      const subtotal = cartItems.value.reduce(
        (sum, item) => sum + item.price * item.quantity, 0
      )
      return subtotal * discountRate.value
    })
    
    const discountRate = computed(() => {
      return user.discountRates[user.level] || 1
    })
    
    // 状态标志
    const discountApplied = ref(false)
    const cartChanged = ref(false)
    
    // 1. 监听用户等级变化,应用折扣
    watch(
      () => user.level,
      (newLevel) => {
        console.log(`用户等级变更为: ${newLevel}`)
        discountApplied.value = true
        
        // 3秒后隐藏折扣提示
        setTimeout(() => {
          discountApplied.value = false
        }, 3000)
      }
    )
    
    // 2. 深度监听购物车变化
    watch(
      cartItems,
      (newItems, oldItems) => {
        console.log('购物车内容变化:', newItems)
        cartChanged.value = true
        
        // 5秒后重置变化标志
        setTimeout(() => {
          cartChanged.value = false
        }, 5000)
      },
      { deep: true }
    )
    
    // 3. 使用 watchEffect 自动跟踪依赖
    const stopEffect = watchEffect(() => {
      console.log(`当前总价: ${totalPrice.value} (用户: ${user.name}, 等级: ${user.level})`)
    })
    
    // 方法
    const addItem = () => {
      const newId = cartItems.value.length + 1
      cartItems.value.push({
        id: newId,
        name: `商品${String.fromCharCode(64 + newId)}`,
        price: Math.round(Math.random() * 200 + 50),
        quantity: 1
      })
    }
    
    const removeItem = () => {
      if (cartItems.value.length > 0) {
        cartItems.value.pop()
      }
    }
    
    const changeUser = () => {
      const levels = ['silver', 'gold', 'platinum']
      const currentIndex = levels.indexOf(user.level)
      const nextIndex = (currentIndex + 1) % levels.length
      user.level = levels[nextIndex]
      
      // 随机更改用户名
      const names = ['张三', '李四', '王五', '赵六']
      user.name = names[Math.floor(Math.random() * names.length)]
    }
    
    return {
      cartItems,
      user,
      totalPrice,
      discountApplied,
      cartChanged,
      addItem,
      removeItem,
      changeUser
    }
  }
}
</script>

<style>
/* 简单样式 */
button {
  margin: 5px;
  padding: 8px 16px;
  cursor: pointer;
}
</style>

四、总结

Vue 3 提供了强大的监听属性功能,使得开发者能够灵活地响应数据的变化并执行相应的逻辑。通过选项式 API 和组合式 API 两种方式,开发者可以根据具体需求选择最适合的方式来定义监听器。此外,监听器还支持多种选项,如 immediatedeep,进一步增强了其功能。


网站公告

今日签到

点亮在社区的每一天
去签到