自学内容网 自学内容网

vue3之computed计算属性

在 Vue 3 中,computed 是一个用于创建计算属性的 API。计算属性是基于其依赖项进行缓存的属性,只有在其依赖项发生变化时才会重新计算。它们通常用于处理复杂的逻辑或数据转换,以便在模板中使用。

使用方法

在 Vue 3 中,可以使用 computed 函数来创建计算属性。

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

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

参数

computed 函数接受一个函数作为参数,这个函数返回计算属性的值。
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。

1. 只读计算属性
const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`;
});

只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建

2. 可读写计算属性
<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.value = 'John Doe' 时,setter 会被调用而 firstName 和 lastName 会随之更新。

注意

1. Getter 不应有副作用

计算属性的 getter 应该是纯粹的计算,不应该有任何副作用。副作用是指在计算过程中改变其他状态、进行异步请求或更改 DOM 等操作。计算属性的主要职责是根据其他响应式状态派生出一个新的值。

错误示例:在计算属性的 getter 中进行异步请求或更改状态。

import { ref, computed } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const data = ref(null);

    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      data.value = await response.json();
    };

    // 错误:在计算属性中进行异步请求
    const computedData = computed(() => {
      fetchData();
      return data.value;
    });

    return {
      count,
      computedData,
    };
  },
};

正确示例:使用侦听器(watcher)来处理副作用。

import { ref, computed, watch } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const data = ref(null);

    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      data.value = await response.json();
    };

    // 正确:在侦听器中处理副作用
    watch(count, () => {
      fetchData();
    });

    const computedData = computed(() => {
      return data.value;
    });

    return {
      count,
      computedData,
    };
  },
};
2. 避免直接修改计算属性值

计算属性的返回值是派生状态,应该被视为只读的。直接修改计算属性的值是没有意义的,应该通过更新其依赖的源状态来触发新的计算。

错误示例:直接修改计算属性的值。

import { ref, computed } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`;
    });

    // 错误:直接修改计算属性的值
    const updateFullName = () => {
      fullName.value = 'Jane Smith'; // 这会导致错误
    };

    return {
      firstName,
      lastName,
      fullName,
      updateFullName,
    };
  },
};

正确示例:通过更新源状态来触发新的计算。

import { ref, computed } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`;
    });

    // 正确:通过更新源状态来触发新的计算
    const updateFullName = () => {
      firstName.value = 'Jane';
      lastName.value = 'Smith';
    };

    return {
      firstName,
      lastName,
      fullName,
      updateFullName,
    };
  },
};

计算属性缓存 vs 方法

计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。意味着只要 firstNamelastName 不改变,无论多少次访问 fullName 都会立即返回先前的计算结果,而不用重复执行 getter 函数。

这也解释了为什么下面的计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖:

const now = computed(() => Date.now())

相比之下,方法调用总是会在重渲染发生时再次执行函数

为什么需要缓存呢?

想象一下我们有一个非常耗性能的计算属性 list,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list。没有缓存的话,我们会重复执行非常多次 list 的 getter,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。

1. 计算属性
  1. 缓存:计算属性是基于其依赖项进行缓存的。只有当依赖项发生变化时,计算属性才会重新计算。
  2. 声明式:计算属性声明了一个依赖关系,当依赖的响应式数据发生变化时,计算属性会自动更新。
  3. 性能优化:由于计算属性是缓存的,因此在依赖项不变的情况下,多次访问计算属性不会触发多次计算。
import { ref, computed } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    // 创建计算属性
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`;
    });

    return {
      firstName,
      lastName,
      fullName,
    };
  },
};

在这个示例中,fullName 是一个计算属性,它依赖于 firstNamelastName。当 firstNamelastName 发生变化时,fullName 会自动更新,并且在依赖项不变的情况下,多次访问 fullName 不会触发多次计算。

2. 方法
  1. 不缓存:方法在每次调用时都会重新执行,不会进行缓存。
  2. 命令式:方法是命令式的,每次调用都会执行相同的逻辑。
  3. 灵活性:方法可以接受参数,适用于需要动态传递参数的场景。
import { ref } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    // 创建方法
    const getFullName = () => {
      return `${firstName.value} ${lastName.value}`;
    };

    return {
      firstName,
      lastName,
      getFullName,
    };
  },
};

在这个示例中,getFullName 是一个方法,每次调用都会重新计算 firstNamelastName 的组合。

3. 计算属性 vs 方法
特性计算属性(Computed Properties)方法(Methods)
缓存
声明式
性能优化
动态参数
适用场景数据转换、复杂逻辑、依赖多个状态事件处理、动态计算、副作用

为 computed() 标注类型

computed() 会自动从其计算函数的返回值上推导出类型:

import { ref, computed } from 'vue'

const count = ref(0)

// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)

// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')

你还可以通过泛型参数显式指定类型:

const double = computed<number>(() => {
  // 若返回值不是 number 类型则会报错
})

原文地址:https://blog.csdn.net/daoshen1314/article/details/142898721

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!