前言

在日常开发中我们可能会有这样的需求,当页面中某个变量发生改变时要同时修改另一个变量的值。一般我们会通过监听器或者回调事件的方式来实现同步更改,但是如果修改次数过多,内容复杂时就会导致代码冗余。为了解决这一问题,Vue3提供了 computed 计算属性。

什么是 computed 计算属性

computed属性在Vue3中是一个非常重要的响应式特性。它可以根据其他响应式数据的变化自动更新自身的值,无需手动操作。computed属性可以接收一个函数作为参数,这个函数会在需要时被调用,并使用其他响应式数据的值进行计算。当任何参与计算的响应式数据发生变化时, computed 属性会自动重新计算其值,并触发相关的依赖项更新。这使得在Vue应用程序中处理复杂的逻辑和数据操作变得更加方便和高效。

computed计算属性的使用

<script setup>
import { reactive, computed } from 'vue'

const dept = reactive({
  name: 'Department01',
  stuffs: [
    'Zhang San',
    'Li Si',
    'Wang Wu'
  ]
})

// 一个计算属性 ref
const numberofStuff = computed(() => {
//将dept变量中stuffs列表的长度赋值给numberofStuff
  return dept.stuffs.length  
})
</script>

<template>
  <p>The number of stuff in Department01:</p>
  <span>{{ numberofStuff }}</span>
</template>

上述代码中numberofStuff是一个记录了部门员工数量的计算属性,会随着dept的变化而变化。

注意点

默认只读

computed计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。但在某些特殊的业务场景下需要我们实现对computed的手动修改,那么可以通过同时提供 getter 和 setter 来创建计算属性的可写方法。

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // 注意:我们这里使用的是解构赋值语法
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

通过上述代码,可以实现通过对fullName的手动赋值修改firstName以及lastName的效果,使computed属性具备了可写能力。

Getter 不应有副作用

计算属性的 getter 应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。举例来说,不要改变其他状态、在 getter 中做异步请求或者更改 DOM!一个计算属性的声明中描述的是如何根据其他值派生一个值。因此 getter 的职责应该仅为计算和返回该值。

避免直接修改计算属性值

从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。

使用场景

过滤和排序

当我们需要根据其他响应式数据进行数据过滤和排序时,可以使用 computed 属性来计算得出过滤和排序后的结果。例如:

<script setup>
import { computed, reactive } from 'vue'

const state = reactive({
  todos: [
    { id: 1, text: '学习Vue3', done: false },
    { id: 2, text: '学习React', done: false },
    { id: 3, text: '学习Angular', done: true }
  ],
  filter: 'all'
})

const filteredTodos = computed(() => {
  if (state.filter === 'all') {
    return state.todos
  } else if (state.filter === 'active') {
    return state.todos.filter(todo => !todo.done)
  } else if (state.filter === 'completed') {
    return state.todos.filter(todo => todo.done)
  }
})

console.log(filteredTodos.value) // 输出:[{ id: 1, text: '学习Vue3', done: false }, { id: 2, text: '学习React', done: false }, { id: 3, text: '学习Angular', done: true }]

state.filter = 'active'

console.log(filteredTodos.value) // 输出:[{ id: 1, text: '学习Vue3', done: false }, { id: 2, text: '学习React', done: false }]
</script>

数组计算

当我们需要对一个数组进行计算时,可以使用computed属性来计算得出数组的值。例如:

<script setup>
import { computed, reactive } from 'vue'

const state = reactive({
  todos: [
    { id: 1, text: '学习Vue3', done: false },
    { id: 2, text: '学习React', done: false },
    { id: 3, text: '学习Angular', done: true }
  ]
})

const totalTodos = computed(() => {
  return state.todos.length
})

const completedTodos = computed(() => {
  return state.todos.filter(todo => todo.done).length
})

console.log(totalTodos.value) // 输出:3
console.log(completedTodos.value) // 输出:1
</script>

计算属性缓存 vs 方法

比较下面两段代码:

// 一个计算属性 ref
const numberofStuff = computed(() => {
//将dept变量中stuffs列表的长度赋值给numberofStuff
  return dept.stuffs.length  
})
function getNumberofStuff(){
//函数返回dept变量中stuffs列表的长度
    return dept.stuffs.length
}

它们分别使用了计算属性和函数方法来获取dept变量下stuffs的列表长度。两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。也就是说一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要目标变量不发生变化就不会重复执行 getter 函数。相比之下,方法调用总是会在重渲染发生时再次执行函数。

当需要拼接生成一个非常耗性能的复杂列表时,相较于函数方法使用计算属性可以大幅提升代码运行效率。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐