跳至主要內容

computed 计算属性

wzCoding大约 4 分钟VueVue3computed 计算属性

computed 计算属性

计算属性 computed 是 Vue3 中的一个重要特性,它可以将一个方法转换为响应式的属性。当计算属性的依赖项发生变化时,计算属性会自动更新。

computed 是一个函数,它通常接受一个 getter 函数作为参数,getter 函数中要依赖其他的响应式数据。computed 函数返回一个 ref 对象,通过 .value 来访问计算属性的值

基本用法

computed 计算属性有两种用法:

  • 只读的计算属性:仅传入 getter 函数作为参数,返回一个只读的响应式属性
  • 可读可写的计算属性:传入 getter 和 setter 函数作为参数,返回一个可读可写的响应式属性

只读的计算属性

这是最常用的计算属性的使用方法,它只传入一个 getter 函数作为参数,返回一个只读的响应式属性。

import { computed } from 'vue';

const count = ref(0);
const doubleCount = computed(() => count.value * 2);

console.log(doubleCount.value); // 输出 0

count.value = 1;
console.log(doubleCount.value); // 输出 2

可读可写的计算属性

这是计算属性的另一种用法,它以配置了 getter 和 setter 函数的对象作为参数,返回一个可读可写的响应式属性。它不仅能够在依赖变化时自动更新自身的值,也可以在自身的值变化时更新依赖的值。

import { computed } from 'vue';

const count = ref(0);
const doubleCount = computed({
  get: () => count.value * 2,
  set: (newValue) => {
    count.value = newValue / 2;
  }
});

console.log(doubleCount.value); // 输出 0

doubleCount.value = 4;
console.log(count.value); // 输出 2

count.value = 3;
console.log(doubleCount.value); // 输出 6

计算属性缓存

计算属性会缓存其结果,计算属性在第一次计算出结果后,只有当计算属性依赖的响应式数据发生变化时,才会重新计算。如果多次访问计算属性,Vue 会自动缓存计算结果,并在下次访问时直接返回缓存的结果,以提高性能。

计算属性与方法的区别

计算属性与方法的主要区别在于计算属性的值会根据其依赖的响应式数据自动更新,而方法则需要手动更新。

实现原理

computed 计算属性的实现原理是利用了 Vue 的 响应式系统。在响应式系统中,通过 effect 方法来执行副作用函数。

computed 是懒执行的,它在内部有着自身的 effect 方法,并在内部通过缓存与懒执行的标识符来控制计算属性的缓存与更新。

以下是简单的代码实现:

computed

function computed(getter) {
  //定义 value 变量来缓存副作用函数执行结果 
  let value;
  //定义 dirty 标识符来控制是否需要重新触发执行副作用函数
  let dirty = true;
  //定义懒执行的副作用函数
  const effectFn = effect(getter,{
     //定义 lazy 标识符来控制是否需要懒执行
     lazy:true,
     //定义 scheduler 调度器来控制副作用函数的执行时机
     scheduler() {
       if(!dirty){
          // 将 dirty 标识符重置为 true
          dirty = true;
          //在这里手动触发执行依赖(value)
          trigger(computedRef, 'value');
       }
     }
  });
  //定义计算属性的计算结果(ref对象)
  const computedRef = {
    //定义 value 属性来访问计算属性的值
    get value() {
      //通过判断 dirty 标识来控制是否执行副作用函数
      if (dirty) {
        //计算属性的计算结果
        value = effectFn();
        //计算出结果后将 dirty 标识符重置为 false,表示当再次访问计算属性的 value 值时,
        //不需要再次执行副作用函数,直接取用缓存的 value 值即可
        dirty = false;
      }
      //在这里手动收集计算属性的依赖(value)
      track(computedRef, 'value');
      return value;
    }
  };

  return computedRef;
}

effect

//之前定义的 effect 方法进行部分改造
function effect(fn, options = {}) {
    //将副作用函数赋值给全局变量,表示当前执行的副作用函数
    const effectFn = ()=> {
        //在执行当前传入的副作用函数前,需要先清理掉上一次旧的副作用函数
        cleanup(effectFn)
        //将当前执行的副作用函数赋值给之前定义的全局变量,以便后续使用(判断)
        activeEffect = effectFn
        //模拟函数调用栈,将当前执行的副作用函数压入栈中保存
        effectStack.push(effectFn)
        //执行副作用函数
        const res = fn()
        //执行完副作用函数后,将当前执行的副作用函数弹出栈
        effectStack.pop()
        //将全局变量的值恢复为上一个副作用函数
        activeEffect = effectStack[effectStack.length-1]
        //返回副作用函数的执行结果
        return res
    }
    //给 effectFn 添加一个配置对象,用于存储副作用函数的调度执行逻辑
    effectFn.options = options
    //给 effectFn 添加 deps 属性,用于存储与响应式数据相关副作用函数的集合
    effectFn.deps = []
    //判断是否需要懒执行
    if(!options.lazy){
      //执行副作用函数
      effectFn()
    }
    return effectFn
}