侧边栏壁纸
博主头像
Fonda's Lab 博主等级

关山难越,谁悲失路之人?萍水相逢,尽是他乡之客。

  • 累计撰写 49 篇文章
  • 累计创建 27 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Vue 3 响应式原理

LouisFonda
2024-06-01 / 0 评论 / 0 点赞 / 20 阅读 / 0 字 / 正在检测是否收录...

Vue 3 响应式原理

Vue 3 引入了全新的响应式系统,它使得数据变化能够自动触发相关的更新,从而让我们能够轻松地构建动态应用。本文将深入探讨 Vue 3 的响应式原理,解释响应式系统的核心机制,并逐步实现一个简化版的响应式系统。

什么是响应式

响应式编程是一种编程范式,其中数据的变化会自动触发相关的计算和 UI 更新。在 Vue 3 中,当我们创建一个响应式对象时,对象的属性发生变化会自动触发依赖这些属性的函数(称为副作用函数)的重新执行。

track 和 trigger

在 Vue 3 的响应式系统中,tracktrigger 是两个核心函数。track 用于追踪依赖关系,而 trigger 则在依赖的数据发生变化时触发相应的副作用函数。

  • track(target, key):记录当前活跃的副作用函数,并将其与目标对象的属性关联。
  • trigger(target, key):在目标对象的属性发生变化时,触发所有依赖该属性的副作用函数。

实现 track 和 trigger 并测试效果

首先,我们实现这两个函数并手动调用 tracktrigger 来测试效果:

const targetMap = new WeakMap();
let activeEffect = null;

function track(target, key) {
  if (activeEffect) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
      depsMap.set(key, (dep = new Set()));
    }
    dep.add(activeEffect);
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const deps = depsMap.get(key);
  if (deps) {
    deps.forEach(effect => effect());
  }
}

// 测试用例
let product = { price: 5, quantity: 2 };
let total;

function effect() {
  total = product.price * product.quantity;
  console.log(`Total is: ${total}`);
}

activeEffect = effect;
track(product, 'price');
track(product, 'quantity');
activeEffect = null;

product.price = 10;
trigger(product, 'price'); // 打印 "Total is: 20"

引入 Proxy 并实现数据劫持

为了自动追踪依赖,我们使用 Proxy 来拦截对象的 getset 操作。Proxy 是一种元编程的技术,可以用来定义对象基本操作的自定义行为(如属性访问、赋值等)。

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      track(target, key);
      return result;
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(target, key);
      }
      return result;
    }
  };
  return new Proxy(target, handler);
}

// 测试用例
const product = reactive({ price: 5, quantity: 2 });
effect = () => {
  total = product.price * product.quantity;
  console.log(`Total is: ${total}`);
};

activeEffect = effect;
effect();
activeEffect = null;

product.price = 10; // 打印 "Total is: 20"

引入 activeEffect 自动追踪副作用

为了能够支持多个副作用函数,我们需要一个全局变量 activeEffect 来保存当前的副作用函数,并自动将其添加到正确的依赖集合中。

function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

// 测试用例
effect(() => {
  total = product.price * product.quantity;
  console.log(`Total is: ${total}`);
});

product.quantity = 3; // 打印 "Total is: 30"

引入 ref 处理基本数据类型

reactive 函数适用于对象,但无法处理基本数据类型(如 numberstring)。为了解决这个问题,我们引入 ref

function ref(raw) {
  const r = {
    get value() {
      track(r, 'value');
      return raw;
    },
    set value(newVal) {
      if (raw !== newVal) {
        raw = newVal;
        trigger(r, 'value');
      }
    }
  };
  return r;
}

// 测试用例
const count = ref(0);
effect(() => {
  console.log(`Count is: ${count.value}`);
});

count.value = 1; // 打印 "Count is: 1"

实现计算属性

计算属性在 Vue 3 中是基于依赖自动更新的属性。我们可以使用 refeffect 来实现计算属性。

function computed(getter) {
  const result = ref();
  effect(() => (result.value = getter()));
  return result;
}

// 测试用例
const product = reactive({ price: 5, quantity: 2 });
const total = computed(() => product.price * product.quantity);

effect(() => {
  console.log(`Total is: ${total.value}`);
});

product.price = 10; // 打印 "Total is: 20"

实现 watch 函数

watch 函数用于监听特定响应式数据的变化,并在数据变化时执行指定的回调函数。

function watch(source, callback) {
  let getter;
  if (typeof source === 'function') {
    getter = source;
  } else {
    getter = () => source.value;
  }

  let oldValue, newValue;
  const effectFn = () => {
    newValue = getter();
    callback(newValue, oldValue);
    oldValue = newValue;
  };

  effect(effectFn);
}

// 测试用例
watch(
  () => product.price,
  (newPrice, oldPrice) => {
    console.log(`Price changed from ${oldPrice} to ${newPrice}`);
  }
);

product.price = 10; // 打印 "Price changed from 5 to 10"

总结

通过本文,我们深入探讨了 Vue 3 响应式系统的核心原理,并逐步实现了一个简化版的响应式系统,涵盖了依赖追踪 (track)、触发更新 (trigger)、使用 Proxy 实现数据劫持、处理基本数据类型的 ref、创建计算属性以及监听数据变化的 watch 函数。这些步骤帮助我们全面理解了 Vue 3 如何实现响应式编程,从而构建高效、动态的应用。

源代码

0

评论区