在复杂的表单或购物车场景中,如何利用 Vue 的计算属性和侦听器,实现数据的实时联动和校验?

各位观众老爷,大家好!今天咱就来聊聊 Vue 里那些个“算计”和“偷听”的门道,看看怎么用计算属性和侦听器,把复杂的表单和购物车玩得溜溜的。

开场白:表单界的“华容道”和购物车里的“俄罗斯方块”

咱们搞前端的,谁还没跟表单和购物车打过交道啊?那玩意儿,看着简单,真要细抠起来,数据联动、实时校验,那简直就是表单界的“华容道”,购物车里的“俄罗斯方块”,一步走错,满盘皆输。

别怕!Vue 早就给咱准备好了两大法宝:计算属性 (computed properties) 和侦听器 (watchers)。这俩玩意儿,一个擅长“算计”,一个精于“偷听”,配合起来,保准把你的表单和购物车安排得明明白白的。

第一节:计算属性:数据“算计”大师

计算属性,顾名思义,就是通过计算得来的属性。它就像一个“懒人计算器”,只要依赖的数据一变,它就会自动重新计算,并把结果缓存起来。下次再访问的时候,直接拿缓存,省时省力。

1.1 计算属性的基本用法

先来个简单的例子:

<template>
  <div>
    <p>单价:{{ price }}</p>
    <p>数量:{{ quantity }}</p>
    <p>总价:{{ totalPrice }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      price: 10,
      quantity: 2,
    };
  },
  computed: {
    totalPrice() {
      return this.price * this.quantity;
    },
  },
};
</script>

在这个例子里,totalPrice 就是一个计算属性。它依赖于 pricequantity 这两个数据。只要 pricequantity 发生变化,totalPrice 就会自动重新计算。

1.2 计算属性的 Getter 和 Setter

计算属性默认只有 getter,也就是只能读取,不能设置。但如果你想让计算属性也能被设置,就可以使用 getter 和 setter。

<template>
  <div>
    <p>全名:{{ fullName }}</p>
    <input v-model="fullName" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: "张",
      lastName: "三",
    };
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + " " + this.lastName;
      },
      set(newValue) {
        const names = newValue.split(" ");
        this.firstName = names[0];
        this.lastName = names[names.length - 1];
      },
    },
  },
};
</script>

在这个例子里,fullName 既可以读取,也可以设置。当你修改 fullName 的值时,firstNamelastName 也会随之改变。

1.3 计算属性在表单中的应用

假设你有一个表单,需要根据用户输入的年龄来判断他是否成年。

<template>
  <div>
    <label>年龄:</label>
    <input type="number" v-model.number="age" />
    <p>是否成年:{{ isAdult }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: 18,
    };
  },
  computed: {
    isAdult() {
      return this.age >= 18 ? "是" : "否";
    },
  },
};
</script>

在这个例子里,isAdult 会根据 age 的值自动判断用户是否成年。

1.4 计算属性在购物车中的应用

假设你有一个购物车,需要计算商品的总价和数量。

<template>
  <div>
    <ul>
      <li v-for="item in cart" :key="item.id">
        {{ item.name }} - {{ item.price }} x {{ item.quantity }}
      </li>
    </ul>
    <p>总价:{{ totalPrice }}</p>
    <p>总数量:{{ totalQuantity }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      cart: [
        { id: 1, name: "苹果", price: 5, quantity: 2 },
        { id: 2, name: "香蕉", price: 3, quantity: 3 },
      ],
    };
  },
  computed: {
    totalPrice() {
      return this.cart.reduce((total, item) => total + item.price * item.quantity, 0);
    },
    totalQuantity() {
      return this.cart.reduce((total, item) => total + item.quantity, 0);
    },
  },
};
</script>

在这个例子里,totalPricetotalQuantity 会根据 cart 里的商品信息自动计算总价和总数量。

第二节:侦听器:数据“偷听”达人

侦听器,顾名思义,就是用来侦听数据变化的。它就像一个“情报员”,时刻关注着指定的数据,一旦发现变化,就会立即执行相应的操作。

2.1 侦听器的基本用法

<template>
  <div>
    <p>年龄:{{ age }}</p>
    <button @click="age++">增长年龄</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: 18,
    };
  },
  watch: {
    age(newValue, oldValue) {
      console.log(`年龄从 ${oldValue} 变成了 ${newValue}`);
    },
  },
};
</script>

在这个例子里,watch 对象中的 age 属性就是一个侦听器。它会侦听 age 数据的变化,并在 age 发生变化时,执行相应的回调函数。回调函数会接收两个参数:newValueoldValue,分别表示新的值和旧的值。

2.2 侦听器的选项:deepimmediate

侦听器还有两个常用的选项:deepimmediate

  • deep 用于侦听对象内部属性的变化。默认情况下,侦听器只能侦听对象本身的引用是否发生变化,而不能侦听对象内部属性的变化。如果需要侦听对象内部属性的变化,就需要将 deep 设置为 true

  • immediate 用于在组件初始化时立即执行一次回调函数。默认情况下,侦听器只会在数据发生变化时执行回调函数。如果需要在组件初始化时立即执行一次回调函数,就需要将 immediate 设置为 true

2.3 侦听器在表单中的应用

假设你有一个表单,需要根据用户输入的邮箱地址来判断邮箱格式是否正确。

<template>
  <div>
    <label>邮箱:</label>
    <input type="email" v-model="email" />
    <p v-if="emailError">{{ emailError }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      email: "",
      emailError: "",
    };
  },
  watch: {
    email(newValue) {
      if (!newValue) {
        this.emailError = "";
        return;
      }
      const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
      if (!emailRegex.test(newValue)) {
        this.emailError = "邮箱格式不正确";
      } else {
        this.emailError = "";
      }
    },
  },
};
</script>

在这个例子里,email 侦听器会侦听 email 数据的变化,并根据邮箱格式是否正确来设置 emailError 的值。

2.4 侦听器在购物车中的应用

假设你有一个购物车,需要在商品数量发生变化时,更新总价和总数量。

<template>
  <div>
    <ul>
      <li v-for="item in cart" :key="item.id">
        {{ item.name }} - {{ item.price }} x
        <input type="number" v-model.number="item.quantity" />
      </li>
    </ul>
    <p>总价:{{ totalPrice }}</p>
    <p>总数量:{{ totalQuantity }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      cart: [
        { id: 1, name: "苹果", price: 5, quantity: 2 },
        { id: 2, name: "香蕉", price: 3, quantity: 3 },
      ],
    };
  },
  computed: {
    totalPrice() {
      return this.cart.reduce((total, item) => total + item.price * item.quantity, 0);
    },
    totalQuantity() {
      return this.cart.reduce((total, item) => total + item.quantity, 0);
    },
  },
  watch: {
    cart: {
      handler() {
        // 触发 totalPrice 和 totalQuantity 的重新计算
        // 这里只是为了演示,实际上 totalPrice 和 totalQuantity 会自动更新
        console.log("购物车数据已更新");
      },
      deep: true,
    },
  },
};
</script>

在这个例子里,cart 侦听器会侦听 cart 数据的变化,并在 cart 里的商品数量发生变化时,触发 totalPricetotalQuantity 的重新计算。 由于cart是一个对象数组,所以要使用 deep: true 才能侦听到数组内部对象属性的变化。

第三节:计算属性和侦听器的选择

既然计算属性和侦听器都能实现数据的实时联动和校验,那什么时候该用哪个呢?

一般来说,可以遵循以下原则:

  • 如果一个数据的变化会导致另一个数据的变化,并且这个变化是同步的,那么可以使用计算属性。 比如,总价是单价和数量的乘积,那么总价就可以使用计算属性来计算。
  • 如果一个数据的变化会导致一些副作用,比如发送网络请求、修改 DOM 元素等,那么可以使用侦听器。 比如,当用户输入邮箱地址时,需要验证邮箱格式是否正确,并显示错误提示,那么就可以使用侦听器来实现。
  • 如果需要侦听对象内部属性的变化,那么必须使用侦听器,并且需要将 deep 设置为 true

简单来说,计算属性擅长“计算”,侦听器擅长“观察”。

第四节:进阶技巧:lodash.debouncelodash.throttle

在某些情况下,数据的变化可能会非常频繁,导致计算属性或侦听器被频繁调用,影响性能。这时,可以使用 lodash.debouncelodash.throttle 来优化性能。

  • lodash.debounce 用于防抖。它可以将多次连续的调用合并成一次调用。比如,当用户在输入框中输入内容时,可以使用 lodash.debounce 来延迟发送网络请求,避免频繁发送请求。

  • lodash.throttle 用于节流。它可以限制函数的调用频率。比如,当用户滚动页面时,可以使用 lodash.throttle 来限制某些函数的调用频率,避免页面卡顿。

4.1 使用 lodash.debounce

<template>
  <div>
    <label>搜索:</label>
    <input type="text" v-model="searchText" />
  </div>
</template>

<script>
import { debounce } from "lodash";

export default {
  data() {
    return {
      searchText: "",
    };
  },
  watch: {
    searchText(newValue) {
      this.debouncedSearch(newValue);
    },
  },
  mounted() {
    this.debouncedSearch = debounce(this.search, 500); // 500ms 防抖
  },
  methods: {
    search(text) {
      console.log("发送搜索请求:", text);
      // 模拟发送搜索请求
    },
  },
};
</script>

在这个例子里,debouncedSearch 函数使用了 lodash.debounce 来进行防抖。只有在用户停止输入 500 毫秒后,才会发送搜索请求。

4.2 使用 lodash.throttle

<template>
  <div>
    <p>滚动距离:{{ scrollY }}</p>
  </div>
</template>

<script>
import { throttle } from "lodash";

export default {
  data() {
    return {
      scrollY: 0,
    };
  },
  mounted() {
    window.addEventListener("scroll", this.throttledUpdateScrollY);
    this.throttledUpdateScrollY = throttle(this.updateScrollY, 200); // 200ms 节流
  },
  beforeDestroy() {
    window.removeEventListener("scroll", this.throttledUpdateScrollY);
  },
  methods: {
    updateScrollY() {
      this.scrollY = window.scrollY;
    },
  },
};
</script>

在这个例子里,throttledUpdateScrollY 函数使用了 lodash.throttle 来进行节流。每 200 毫秒才会更新一次 scrollY 的值。

第五节:总结:让数据联动飞起来

今天,咱们一起学习了 Vue 中计算属性和侦听器的用法,以及它们在表单和购物车中的应用。希望大家能够灵活运用这两种工具,让你的数据联动飞起来,让你的用户体验更上一层楼!

记住,计算属性擅长“计算”,侦听器擅长“观察”。选择合适的工具,才能事半功倍。

表格总结:

特性 计算属性 (Computed Properties) 侦听器 (Watchers)
用途 计算并缓存派生数据 响应数据变化执行副作用
触发 依赖的数据变化 被侦听的数据变化
执行 同步 异步 (可以)
返回值 必须返回一个值 无需返回值
场景 总价计算、过滤列表 数据校验、发送请求、修改 DOM
性能优化 缓存结果,依赖不变不重新计算 可以使用 debouncethrottle
是否可以修改 可以通过 setter 修改 不能直接修改被侦听的数据

各位,下次再见! 希望这些知识能帮助你构建更强大的 Vue 应用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注