Углубленный анализ: как Vue3 умело реализует мощные вычисления

Vue.js

предисловие

Вычисляемое в Vue — очень мощная функция.После изменения значения, доступ к которому осуществляется в вычисляемой функции, вычисляемое значение также изменится автоматически.

Реализация в Vue2 заключается в использованииWatcherВложенная коллекция ,渲染watcherсобралcomputed watcherкак зависимость,computed watcherсобрал снова响应式数据某个属性как зависимость, так в响应式数据某个属性Когда произойдет изменение, оно будет响应式属性 -> computed值更新 -> 视图渲染Такая цепочка триггеров срабатывает в прошлом.Если вам интересен принцип во Vue2, вы можете прочитать мой анализ этой статьи:

Возьмите вас за руку, чтобы реализовать минимальную отзывчивую систему для изучения данных Vue, вычислений, просмотра исходного кода.

Предварительное знание

Чтобы прочитать эту статью, вам нужно сначала изучить основные принципы отзывчивости Vue3.Сначала вы можете прочитать мою статью.Принцип такой же, как и у Vue3:Помогите вам полностью понять принцип ответа прокси Vue3! TypeScript реализует реактивную библиотеку на основе прокси с нуля.

если дляeffect,reactiveЕсли понятия недостаточно знакомы, эта статья временно недоступна.Вы можете сначала прочитать статью выше.

После того, как у вас есть необходимые знания, вы должны знать по умолчанию:

  1. effectПо сути, это функция сбора зависимостей, доступ к реагирующим данным осуществляется внутри нее, а реагирующие данные будутeffectФункция собирается как зависимость, и при следующем изменении реактивных данных она будет запущена для повторного выполнения.

  2. reactiveТо, что возвращается, является ответными данными, которые можно комбинировать сeffectДля использования с.

Возьмите простой каштан:

// 响应式数据
const data = reactive({ count: 0 });
// 依赖收集
effect(() => console.log(data.count));
// 触发上面的effect重新执行
data.count++;

В этом примере данные являются реактивными данными.

Функция, переданная эффектом, обращается к его свойствам внутри.countсейчас,

Итак,count -> effectзависимость.

В следующий раз, когда счетчик изменится, эффект будет выполнен повторно, это так просто.

computed

Затем введите основные понятия в этой статье,computedПосле перезаписи этого примера:

// 1. 响应式数据
const data = reactive({ count: 0 });
// 2. 计算属性
const plusOne = computed(() => data.count + 1);
// 3. 依赖收集
effect(() => console.log(plusOne.value));
// 4. 触发上面的effect重新执行
data.count++;

Такой пример тоже может сработать, почему?data.countизменение энергиинепрямой триггерКак насчет повторного выполнения эффектов, обращающихся к вычисляемым свойствам?

Давайте проанализируем шаг за шагом с одноточечной отладкой.

Исходный код упрощенной версии

Первый взгляд на упрощенную версиюcomputedкод:

export function computed(getter) {
  let dirty = true;
  let value: T;

  // 这里还是利用了effect做依赖收集
  const runner = effect(getter, {
    // 这里保证初始化的时候不去执行getter
    lazy: true,
    computed: true,
    scheduler: () => {
      // 在触发更新时 只是把dirty置为true
      // 而不去立刻计算值 所以计算属性有lazy的特性
      dirty = true;
    },
  });
  return {
    get value() {
      if (dirty) {
        // 在真正的去获取计算属性的value的时候
        // 依据dirty的值决定去不去重新执行getter 获取最新值
        value = runner();
        dirty = false;
      }
      // 这里是关键 后续讲解
      trackChildRun(runner);
      return value;
    },
    set value(newValue: T) {
      setter(newValue);
    },
  };
}

Как видите, вычисление на самом делеeffect. Здесь ловко используется замыкание, и несколько ключевых моментов в аннотации определяют, что вычисляемое свойство имеет懒加载Когда вы не читаете значение, оно фактически не будет оцениваться.

предварительная подготовка

Первое, что нужно знать, это то, что функция эффекта начнет выполняться немедленно, и перед ее выполнением поставьтеeffect自身стать глобальнымactiveEffect, для реактивных зависимостей сбора данных.

а такжеactiveEffectЗапись имеет форму стека, который помещается в стек, когда функция начинает выполняться, и извлекается из стека, когда выполнение функции заканчивается, чтобы можно было поддерживать взаимосвязь вложенных эффектов.

Давайте начнем с нескольких псевдонимов для простоты объяснения

// 计算effect
computed(() => data.count + 1);
// 日志effect
effect(() => console.log(plusOne.value));

С точки зрения зависимостей,
日志effectчитать计算effect
计算effectчитать реактивные свойстваcount
Так что порядок обновлений тоже должен быть:
count改变 -> 计算effect更新 -> 日志effect更新

Так как же формируется эта цепочка отношений?

пошаговая интерпретация

Когда эффект ведения журнала начнет выполняться,

⭐⭐
На данный момент activeEffect — это эффект журнала.

В настоящее время стек эффектов [эффект журнала]
⭐⭐

Чтение триггеров plusOne.value

 get value() {
      if (dirty) {
        // 在真正的去获取计算属性的value的时候
        // 依据dirty的值决定去不去重新执行getter 获取最新值
        value = runner()
        dirty = false
      }
      // 这里是关键 后续讲解
      trackChildRun(runner)
      return value
},

Сначала введите процесс оценки:value = runner(), бегун на самом деле计算effect, который является оболочкой для функции-получателя, переданной пользователем,

После входа в бегун

⭐⭐
На данный момент activeEffect — это расчетный эффект.

В настоящее время стек эффектов [эффект журнала, эффект расчета]
⭐⭐
бегун завернутый() => data.count + 1то есть计算effectбуду читатьcount, так как это функция, обернутая эффектом, она запускает ответные данныеgetПерехват:

В настоящее времяcountбудет собирать计算effectкак собственная зависимость.

а также计算effectбудет собиратьcountКоллекция зависимостей, хранимая на себе. (пройти черезeffect.depsАтрибуты)

dep.add(activeEffect);
activeEffect.deps.push(dep);

то есть формированиедвусторонняя коллекцияОтношение,

计算effectсохраненcountвсе зависимости отcountтакже сохранен计算effectзависимость.

Затем, когда бегун закончит бег,计算effectиз стека, в это времяactiveEffectстал вершиной стека日志effect

⭐⭐
На данный момент activeEffect — это эффект журнала.

В настоящее время стек эффектов [эффект журнала]
⭐⭐

Введите следующийважные шаги:trackChildRun

trackChildRun(runner);

function trackChildRun(childRunner: ReactiveEffect) {
  for (let i = 0; i < childRunner.deps.length; i++) {
    const dep = childRunner.deps[i];
    dep.add(activeEffect);
  }
}

этоrunnerто есть计算effect,этоdepsвисит наcountнабор зависимостей,

существуетtrackChildRun, он принимает текущий активный эффект, который日志effectтакже присоединилсяcountв наборе зависимостей.

В настоящее времяcountКоллекция зависимостей выглядит так:[ 计算effect, 日志effect ]

так что в следующий разcountПри обновлении оба эффекта будут срабатывать повторно, а потому порядок срабатывания срабатывает первымcomputed effectпост-триггер普通effect, тем самым завершая

  1. Грязь рассчитанного эффекта устанавливается в значение true, что указывает на то, что следующее чтение необходимо переоценить.
  2. Эффект журнала считывает значение вычисленного эффекта, получает последнее значение и распечатывает его.

Суммировать

Я должен признать, что реализация мощной функции вычислений действительно требует очень сложной внутренней реализации, и я верю, что эта двусторонняя процедура сбора зависимостей также принесет большое вдохновение всем вам. Учась с You Da, у вас действительно есть что поесть!

Кроме того, из-за@vue/reactivityНезависимость от фреймворка, я интегрировал его в React и сделал библиотеку управления состоянием, которая может полностью использовать вышеперечисленное.computedи другие мощные возможности Vue3.

react-composition-api

Заинтересованные друзья также могут посмотреть и поставить звезду!