简单实现一个Vue3的computed功能

129 阅读3分钟

前言

最近在学习Vue3的核心知识,觉得 computed 这个Vue中重要的功能的实现逻辑很巧妙很有意思,想加深自己的印象,所以想写个笔记,记录一下。

首先,computed 功能特点之一是,会把计算结果缓存起来,当所依赖的值发生变化,下一次获取值的时候才会重新进行计算。

例子

根据computed的特点,我们将实现以下例子:

// 创建响应式数据
let user = reactive({ age: 10 });

let count = 0;
function getUserAge(){
  count++;  // 记录函数运行的次数
  console.log(`count: ${count}`);

  return user.age;
}

// 创建computed数据
let myUser = computed(getUserAge);


console.log(count === 0); // true - 函数没有执行过

myUser.value; 
console.log(count === 1); // true - 首次获取依赖值,需执行函数来获取

myUser.value;
console.log(count === 2); // false - 所依赖的值未发生变化,再次获取,返回的是缓存值,未执行函数

user.age = 30;
console.log(count); // 1 - 所依赖的值虽然发生变化,但未进行获取myUser的值,所以不会执行函数

myUser.value;
console.log(count); // 2 - 所依赖的值发生变化,且再次取值,会执行函数

实现

  1. 响应式数据

reactive 函数用于创建响应式数据,vue3中利用 Proxy API来实现,代理对给定对象的操作,在属性 get 的时候收集依赖,在属性 set 的时候触发依赖,这就是vue的响应式原理的大致实现方式。

function reactive(obj){
  return new Proxy(obj, {
    get(target, key){
      // 收集依赖
      track(target, key);
      let res = Reflect.get(target, key);
      return res;
    },
    set(target, key, newValue){
      let res = Reflect.set(target, key, newValue);
      // 触发依赖
      trigger(target, key);
      return res;
    }
  })
}
  1. 依赖收集/触发
let targetMap = new Map();    // 收集每个响应式对象
let activeEffect = undefined; // 当前需要收集的依赖
class ReactiveEffect{
  _fn = undefined;
  _scheduler = undefined;
  deps = undefined;
  constructor(fn, scheduler){
    this._fn = fn;
    this._scheduler = scheduler;
    this.deps = new Set();
  }
  run(){
    activeEffect = this;
    let res = this._fn();
    activeEffect = undefined;
    return res;
  }
}

/**
 * 收集依赖
 * target 目标对象
 * key    对象的key
 */
function track(target, key){
  if(!activeEffect) return;
  // 获取对象对应的map
  let depsMap = targetMap.get(target);
  if(!depsMap) targetMap.set(target, ( depsMap = new Map() ));
  
  // 获取key对应的存放依赖的集合
  let deps = depsMap.get(key);
  if(!deps) depsMap.set(key, ( deps = new Set() ));

  // 收集依赖
  deps.add(activeEffect);
  // 反向收集
  activeEffect.deps.add(deps);
}


/**
 * 触发依赖
 * target 目的对象
 * key    对象的key
 */
function trigger(target, key){
  let depsMap = targetMap.get(target);
  let deps = depsMap.get(key);
  // 触发依赖
  deps.forEach(effect => {
    effect._scheduler ? effect._scheduler() : effect.run();
  })
}
  1. 计算属性
class ComputedImpl{
  _dirty = true;
  _value = undefined;
  _effect = undefined;
  constructor(getter){
    this._effect = new ReactiveEffect(getter, () => { this._dirty = true });
  }
  get value(){
    if(this._dirty){
      this._value = this._effect.run();
      this._dirty = false;
    }
    return this._value;
  }
}

/**
 * 创建computed数据
 * getter 目标函数
 */
function computed(getter){
  return new ComputedImpl(getter);
}

结果

image.png

总文件

let targetMap = new Map();    // 收集每个响应式对象
let activeEffect = undefined; // 当前需要收集的依赖

function reactive(obj){
  return new Proxy(obj, {
    get(target, key){
      // 收集依赖
      track(target, key);
      let res = Reflect.get(target, key);
      return res;
    },
    set(target, key, newValue){
      let res = Reflect.set(target, key, newValue);
      // 触发依赖
      trigger(target, key);
      return res;
    }
  })
}


class ReactiveEffect{
  _fn = undefined;
  _scheduler = undefined;
  deps = undefined;
  constructor(fn, scheduler){
    this._fn = fn;
    this._scheduler = scheduler;
    this.deps = new Set();
  }
  run(){
    activeEffect = this;
    let res = this._fn();
    activeEffect = undefined;
    return res;
  }
}

/**
 * 收集依赖
 * target 目标对象
 * key    对象的key
 */
function track(target, key){
  if(!activeEffect) return;
  // 获取对象对应的map
  let depsMap = targetMap.get(target);
  if(!depsMap) targetMap.set(target, ( depsMap = new Map() ));
  
  // 获取key对应的存放依赖的集合
  let deps = depsMap.get(key);
  if(!deps) depsMap.set(key, ( deps = new Set() ));

  // 收集依赖
  deps.add(activeEffect);
  // 反向收集
  activeEffect.deps.add(deps);
}


/**
 * 触发依赖
 * target 目的对象
 * key    对象的key
 */
function trigger(target, key){
  let depsMap = targetMap.get(target);
  let deps = depsMap.get(key);
  // 触发依赖
  deps.forEach(effect => {
    effect._scheduler ? effect._scheduler() : effect.run();
  })
}


class ComputedImpl{
  _dirty = true;
  _value = undefined;
  _effect = undefined;
  constructor(getter){
    this._effect = new ReactiveEffect(getter, () => { this._dirty = true });
  }
  get value(){
    if(this._dirty){
      this._value = this._effect.run();
      this._dirty = false;
    }
    return this._value;
  }
}

/**
 * 创建computed数据
 * getter 目标函数
 */
function computed(getter){
  return new ComputedImpl(getter);
}

// 创建响应式数据
let user = reactive({ age: 10 });

let count = 0;
function getUserAge(){
  count++;  // 记录函数运行的次数

  return user.age;
}

let myUser = computed(getUserAge);

console.log(count === 0); // true - 函数没有执行过

myUser.value; 
console.log(count === 1); // true - 首次获取依赖值,需执行函数来获取

myUser.value;
console.log(count === 2); // false - 所依赖的值未发生变化,再次获取,返回的是缓存值,未执行函数

user.age = 30;
console.log(count); // 1 - 所依赖的值虽然发生变化,但未进行获取myUser的值,所以不会执行函数

myUser.value;
console.log(count); // 2 - 所依赖的值发生变化,且再次取值,会执行函数