MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue计算属性与侦听器 项目中的典型应用场景

2022-02-166.3k 阅读

Vue计算属性的基本概念

在Vue.js中,计算属性是一种非常强大的特性。它允许我们基于其他响应式数据来定义一个新的属性,这个属性会根据它依赖的数据的变化而自动更新。计算属性本质上是基于Vue的依赖追踪系统实现的。当计算属性所依赖的任何数据发生变化时,计算属性就会重新计算。

例如,假设我们有一个简单的Vue组件,包含两个数据属性 firstNamelastName,我们想要得到一个完整的 fullName。我们可以通过计算属性来实现:

<template>
  <div>
    <p>First Name: <input v-model="firstName"></p>
    <p>Last Name: <input v-model="lastName"></p>
    <p>Full Name: {{ fullName }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: '',
      lastName: ''
    };
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName;
    }
  }
};
</script>

在上述代码中,fullName 就是一个计算属性。每当 firstNamelastName 发生变化时,fullName 都会自动重新计算。

计算属性的缓存机制

计算属性有一个重要的特性就是缓存。Vue会自动缓存计算属性的结果,只有在它依赖的数据发生变化时才会重新计算。这意味着如果计算属性依赖的数据没有改变,多次访问该计算属性时,它不会重新执行函数,而是直接返回缓存的结果。

比如,我们有一个复杂的计算属性,它执行一些耗时的操作,如大量的数组计算或复杂的字符串处理:

<template>
  <div>
    <p>Value: <input v-model="value"></p>
    <p>Computed Result: {{ complexComputed }}</p>
    <p><button @click="printResult">Print Result</button></p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      value: ''
    };
  },
  computed: {
    complexComputed() {
      let result = '';
      // 模拟复杂计算
      for (let i = 0; i < 1000000; i++) {
        result += this.value.charAt(0);
      }
      return result;
    }
  },
  methods: {
    printResult() {
      console.log(this.complexComputed);
    }
  }
};
</script>

在这个例子中,当我们点击按钮多次打印 complexComputed 的值时,如果 value 没有改变,complexComputed 不会重新计算,而是直接返回缓存的值。这大大提高了性能,尤其是在复杂计算的场景下。

计算属性在项目中的应用场景 - 表单验证

在表单处理的项目中,计算属性可以很好地用于表单验证。例如,我们有一个注册表单,需要验证用户名和密码是否符合一定的规则,并且确认密码是否与密码一致。

<template>
  <div>
    <form>
      <label for="username">Username:</label>
      <input type="text" id="username" v-model="username">
      <p v-if="!isValidUsername">Username should be at least 5 characters long.</p>

      <label for="password">Password:</label>
      <input type="password" id="password" v-model="password">
      <p v-if="!isValidPassword">Password should be at least 8 characters long and contain at least one number.</p>

      <label for="confirmPassword">Confirm Password:</label>
      <input type="password" id="confirmPassword" v-model="confirmPassword">
      <p v-if="!isPasswordsMatch">Passwords do not match.</p>

      <button :disabled="!isFormValid">Submit</button>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: '',
      confirmPassword: ''
    };
  },
  computed: {
    isValidUsername() {
      return this.username.length >= 5;
    },
    isValidPassword() {
      const hasNumber = /\d/.test(this.password);
      return this.password.length >= 8 && hasNumber;
    },
    isPasswordsMatch() {
      return this.password === this.confirmPassword;
    },
    isFormValid() {
      return this.isValidUsername && this.isValidPassword && this.isPasswordsMatch;
    }
  }
};
</script>

在上述代码中,isValidUsernameisValidPasswordisPasswordsMatchisFormValid 都是计算属性。它们根据表单输入的值实时计算表单的有效性,并在界面上显示相应的提示信息。同时,isFormValid 用于控制提交按钮的禁用状态,只有当所有验证都通过时,按钮才可用。

计算属性在项目中的应用场景 - 数据过滤与展示

在处理列表数据时,经常需要根据不同的条件对数据进行过滤和展示。计算属性可以方便地实现这一功能。

假设我们有一个商品列表,每个商品都有 namepricecategory 等属性。我们希望根据用户输入的搜索关键词和选择的类别来过滤商品列表。

<template>
  <div>
    <input type="text" v-model="searchKeyword" placeholder="Search products...">
    <select v-model="selectedCategory">
      <option value="">All Categories</option>
      <option value="electronics">Electronics</option>
      <option value="clothes">Clothes</option>
    </select>

    <ul>
      <li v-for="product in filteredProducts" :key="product.id">
        {{ product.name }} - ${{ product.price }} - {{ product.category }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      products: [
        { id: 1, name: 'Laptop', price: 1000, category: 'electronics' },
        { id: 2, name: 'T - Shirt', price: 20, category: 'clothes' },
        { id: 3, name: 'Smartphone', price: 500, category: 'electronics' },
        { id: 4, name: 'Jeans', price: 50, category: 'clothes' }
      ],
      searchKeyword: '',
      selectedCategory: ''
    };
  },
  computed: {
    filteredProducts() {
      let result = this.products;
      if (this.searchKeyword) {
        result = result.filter(product => product.name.toLowerCase().includes(this.searchKeyword.toLowerCase()));
      }
      if (this.selectedCategory) {
        result = result.filter(product => product.category === this.selectedCategory);
      }
      return result;
    }
  }
};
</script>

在这个例子中,filteredProducts 是一个计算属性。它根据 searchKeywordselectedCategory 的值对 products 数组进行过滤。每当搜索关键词或选择的类别发生变化时,filteredProducts 会重新计算,从而实时更新商品列表的展示。

计算属性在项目中的应用场景 - 动态样式绑定

计算属性还可以用于动态地绑定元素的样式。例如,我们有一个进度条组件,需要根据当前的进度值来动态改变进度条的宽度和颜色。

<template>
  <div class="progress - bar">
    <div :class="progressClass" :style="{ width: progressWidth }"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      progress: 0
    };
  },
  computed: {
    progressWidth() {
      return this.progress + '%';
    },
    progressClass() {
      if (this.progress < 30) {
        return 'progress - low';
      } else if (this.progress < 70) {
        return 'progress - medium';
      } else {
        return 'progress - high';
      }
    }
  }
};
</script>

<style scoped>
.progress - bar {
  width: 300px;
  height: 20px;
  background - color: #f0f0f0;
  border - radius: 5px;
}

.progress - low {
  background - color: red;
}

.progress - medium {
  background - color: orange;
}

.progress - high {
  background - color: green;
}
</style>

在上述代码中,progressWidth 计算属性根据 progress 的值动态设置进度条的宽度,progressClass 计算属性根据 progress 的值动态选择进度条的样式类。这样,当 progress 的值发生变化时,进度条的宽度和颜色会相应地改变。

Vue侦听器的基本概念

Vue的侦听器(watchers)提供了一种观察响应式数据变化的机制。与计算属性不同,侦听器更侧重于在数据变化时执行特定的操作,而不仅仅是返回一个值。

我们可以通过 watch 选项来定义一个侦听器。例如,我们有一个简单的计数器组件,当计数器的值发生变化时,我们想要在控制台打印一条消息:

<template>
  <div>
    <p>Counter: {{ counter }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: 0
    };
  },
  methods: {
    increment() {
      this.counter++;
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log(`Counter has changed from ${oldValue} to ${newValue}`);
    }
  }
};
</script>

在这个例子中,counter 是一个数据属性,我们通过 watch 选项定义了一个对 counter 的侦听器。当 counter 的值发生变化时,侦听器函数会被调用,并且会传入新值和旧值。

侦听器的深度监听

在处理复杂对象或数组时,有时我们需要深度监听对象或数组内部的变化。默认情况下,Vue的侦听器只会监听对象或数组的引用变化,而不会监听其内部属性的变化。

例如,我们有一个包含多个属性的用户对象,我们想要监听 user.address.city 的变化:

<template>
  <div>
    <p>City: <input v - model="user.address.city"></p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: 'John',
        address: {
          city: 'New York'
        }
      }
    };
  },
  watch: {
    user: {
      handler(newValue, oldValue) {
        console.log('User object has changed');
      },
      deep: true
    }
  }
};
</script>

在上述代码中,通过设置 deep: true,我们开启了对 user 对象的深度监听。这样,当 user.address.city 发生变化时,handler 函数会被调用。

侦听器在项目中的应用场景 - 数据持久化

在很多项目中,我们需要将用户数据持久化到本地存储(如 localStorage),以便在页面刷新或重新加载时数据不会丢失。侦听器可以很好地实现这一功能。

假设我们有一个待办事项列表应用,用户添加或删除待办事项时,我们需要将列表数据保存到 localStorage 中。

<template>
  <div>
    <input type="text" v - model="newTodo" placeholder="Add a new todo...">
    <button @click="addTodo">Add Todo</button>

    <ul>
      <li v - for="(todo, index) in todos" :key="index">
        {{ todo }}
        <button @click="deleteTodo(index)">Delete</button>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      newTodo: '',
      todos: []
    };
  },
  created() {
    const storedTodos = localStorage.getItem('todos');
    if (storedTodos) {
      this.todos = JSON.parse(storedTodos);
    }
  },
  methods: {
    addTodo() {
      if (this.newTodo) {
        this.todos.push(this.newTodo);
        this.newTodo = '';
      }
    },
    deleteTodo(index) {
      this.todos.splice(index, 1);
    }
  },
  watch: {
    todos: {
      handler(newValue) {
        localStorage.setItem('todos', JSON.stringify(newValue));
      },
      deep: true
    }
  }
};
</script>

在这个例子中,我们在 created 钩子函数中从 localStorage 读取数据并初始化 todos 列表。然后,通过对 todos 数组的深度监听,当 todos 数组中的任何元素发生变化(添加或删除待办事项)时,handler 函数会将最新的 todos 数组保存到 localStorage 中。

侦听器在项目中的应用场景 - 异步操作

侦听器非常适合处理异步操作。例如,当用户输入搜索关键词时,我们需要向服务器发送请求获取搜索结果。

<template>
  <div>
    <input type="text" v - model="searchKeyword" placeholder="Search...">
    <ul>
      <li v - for="result in searchResults" :key="result.id">
        {{ result.title }}
      </li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      searchKeyword: '',
      searchResults: []
    };
  },
  watch: {
    searchKeyword: {
      immediate: true,
      handler(newValue) {
        if (newValue) {
          axios.get(`https://example.com/api/search?q=${newValue}`)
           .then(response => {
              this.searchResults = response.data;
            })
           .catch(error => {
              console.error('Error fetching search results:', error);
            });
        } else {
          this.searchResults = [];
        }
      }
    }
  }
};
</script>

在上述代码中,我们通过设置 immediate: true,使得在组件初始化时就执行一次 handler 函数,这样当页面加载完成,如果 searchKeyword 有初始值,就会立即发起搜索请求。当 searchKeyword 的值发生变化时,handler 函数会根据新的关键词向服务器发送请求,并更新搜索结果。

侦听器在项目中的应用场景 - 路由变化处理

在单页面应用(SPA)中,路由变化是一个常见的场景。我们可以使用侦听器来监听路由的变化,并执行相应的操作。

假设我们有一个多页面的应用,不同页面可能需要不同的页面标题。我们可以通过监听路由变化来动态更新页面标题。

<template>
  <div>
    <router - view></router - view>
  </div>
</template>

<script>
export default {
  watch: {
    '$route'(to, from) {
      if (to.name === 'home') {
        document.title = 'Home Page';
      } else if (to.name === 'about') {
        document.title = 'About Us';
      } else if (to.name === 'contact') {
        document.title = 'Contact Us';
      }
    }
  }
};
</script>

在这个例子中,我们通过监听 $route 的变化,根据不同的路由名称来更新页面的标题。这样,当用户在应用中导航到不同页面时,页面标题会相应地改变。

计算属性与侦听器的选择

在实际项目中,选择使用计算属性还是侦听器取决于具体的需求。

如果我们只是需要基于其他数据生成一个新的值,并且这个值会被多次使用,同时希望有缓存机制来提高性能,那么计算属性是一个很好的选择。例如在表单验证、数据过滤和动态样式绑定等场景中,计算属性能够简洁高效地实现功能。

而当我们需要在数据变化时执行一些副作用操作,如异步操作、数据持久化或路由变化处理等,侦听器则更为合适。侦听器可以更灵活地控制在数据变化前后执行的逻辑,并且可以通过深度监听和 immediate 选项来满足不同的需求。

在一些复杂的场景中,可能会同时使用计算属性和侦听器。例如,我们可以先用计算属性来过滤和处理数据,然后通过侦听器来监听计算属性的变化并执行进一步的操作。

总之,熟练掌握计算属性和侦听器的特性和应用场景,能够让我们在Vue项目开发中更加高效地实现各种功能,提升用户体验和应用性能。