Изображение для пояснения адаптивной системы Vue 3.0.

Vue.js
Изображение для пояснения адаптивной системы Vue 3.0.

Эта статья впервые появилась в моем блоге:«Картинка для пояснения адаптивной системы Vue 3.0»

С выпуском Vue 3.0 Pre Alpha мы получили представление о реализации исходного кода. Одной из лучших особенностей Vue является его система реактивности, и мы можем найти соответствующую реализацию в модуле packages/reactivity репозитория. Хотя кода в исходниках не так много, а аналитических статей в интернете много, все же довольно утомительно четко понимать конкретный процесс реализации принципа отзывчивости. После дня исследований и сортировки я обобщил принцип его отзывчивой системы в виде рисунка, и эта статья также будет посвящена этому изображению, чтобы описать конкретный процесс реализации.

vue 3 响应式系统原理

Статьи, связанные с кодом, я также загрузил всклад, в сочетании с кодом читать эту статью будет более плавно!

базовый пример

Отзывчивая система Vue 3.0 — это независимый модуль, который можно использовать полностью без Vue, поэтому после того, как мы клонируем исходный код, мы можем отлаживать его непосредственно в модуле packages/reactivity.

  1. Запуск в корневом каталоге проектаyarn dev reactivity, затем введитеpackages/reactivityкаталог, чтобы найти выводdist/reactivity.global.jsдокумент.
  2. создать новыйindex.html, напишите следующий код:
<script src="./dist/reactivity.global.js"></script>
<script>
const { reactive, effect } = VueObserver

const origin = {
  count: 0
}
const state = reactive(origin)

const fn = () => {
  const count = state.count
  console.log(`set count to ${count}`)
}
effect(fn)
</script>
  1. Откройте файл в браузере и запустите его в консолиstate.count++, вы можете увидеть выводset count to 1.

В приведенном выше примере мы используемreactive()функция положитьoriginОбъект преобразуется в объект Proxystate;использоватьeffect()функция положитьfn()как реактивный обратный вызов. когдаstate.countКогда происходит изменение, оно запускаетсяfn(). Далее мы будем использовать этот пример в сочетании с приведенной выше блок-схемой, чтобы объяснить, как работает эта адаптивная система.

фаза инициализации

image

На этапе инициализации выполняются две основные операции.

  1. ПучокoriginОбъект преобразуется в реактивный объект Proxy.state.
  2. поставить функциюfn()Как отзывчивая функция ЭФФЕКТ.

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

Как мы все знаем, Vue 3.0 использует Proxy для замены предыдущегоObject.defineProperty(), перезаписывает метод получения/установки объекта для завершения сбора зависимостей и срабатывания ответа. Но на этом этапе мы временно игнорируем то, как он переписывает геттер/сеттер объекта, что будет подробно объяснено в последующем «этапе сбора зависимостей». Для простоты мы можем сократить этот раздел до двухстрочного кода.reactive()функция:

export function reactive(target) {
  const observed = new Proxy(target, handler)
  return observed
}

Полный код находится наreactive.js. здесьhandlerЭто ключ к преобразованию Getter / Setter, мы помещаем его в объяснение.

Далее анализируем вторую вещь.

когда обычная функцияfn()одеялоeffect()После обертывания он становится функцией адаптивного эффекта, иfn()также будетсделай это один раз.

из-заfn()Существуют свойства, ссылающиеся на объект Proxy, поэтому этот шаг вызовет получение объекта, тем самым запустив сбор зависимостей.

Кроме того, функция эффекта также будет помещена в стек с именем «activeReactiveEffectStack» (здесь — effectStack) для использования в последующей коллекции зависимостей.

Давайте посмотрим на код (пожалуйста, посмотрите полный кодeffect.js):

export function effect (fn) {
  // 构造一个 effect
  const effect = function effect(...args) {
    return run(effect, fn, args)
  }
  // 立即执行一次
  effect()
  return effect
}

export function run(effect, fn, args) {
  if (effectStack.indexOf(effect) === -1) {
    try {
      // 往池子里放入当前 effect
      effectStack.push(effect)
      // 立即执行一遍 fn()
      // fn() 执行过程会完成依赖收集,会用到 effect
      return fn(...args)
    } finally {
      // 完成依赖收集后从池子中扔掉这个 effect
      effectStack.pop()
    }
  }
}

На этом этап инициализации завершен. Следующий шаг — самый важный во всей системе — этап сбора зависимостей.

Этап сбора зависимостей

image

Момент срабатывания этой стадии — когда эффект выполняется немедленно, и его внутренниеfn()Когда срабатывает геттер объекта Proxy. Проще говоря, просто сделайте что-то вродеstate.countоператор, он вызовет метод получения состояния.

Наиболее важной целью этапа сбора зависимостей является создание «таблицы сбора зависимостей», которая представляет собой «targetMap», показанную на рисунке. targetMap — это WeakMap, значением ключа которого является ~~ текущий объект Proxy.state~~ объект перед проксиorigin, а значение — это depsMap, соответствующий объекту.

depsMap — это значение атрибута (вот карта, геттер триггера значения ключаcount), В то время как значениеАктивировано значение атрибутасоответствующие эффекты.

Все еще немного круглый? Итак, давайте возьмем другой пример. Предположим, что есть объект Proxy и эффект следующий:

const state = reactive({
  count: 0,
  age: 18
})

const effect1 = effect(() => {
  console.log('effect1: ' + state.count)
})

const effect2 = effect(() => {
  console.log('effect2: ' + state.age)
})

const effect3 = effect(() => {
  console.log('effect3: ' + state.count, state.age)
})

Тогда targetMap здесь должен выглядеть так:

image

так,{ target -> key -> dep }Соответствующие отношения устанавливаются, а коллекция зависимостей завершена.кодследующим образом:

export function track (target, operationType, key) {
  const effect = effectStack[effectStack.length - 1]
  if (effect) {
    let depsMap = targetMap.get(target)
    if (depsMap === void 0) {
      targetMap.set(target, (depsMap = new Map()))
    }

    let dep = depsMap.get(key)
    if (dep === void 0) {
      depsMap.set(key, (dep = new Set()))
    }

    if (!dep.has(effect)) {
      dep.add(effect)
    }
  }
}

Очень важно понимать таблицу сбора зависимостей targetMap, потому что это ядро ​​всей реактивной системы.

фаза реагирования

Вспоминая пример из предыдущей главы, мы получаем{ count: 0, age: 18 }Proxy и конструирует три эффекта. Взгляните на эффект на консоли:

image

Эффект ожидаемый, так как же он достигается? Для начала рассмотрим принципиальную схему этого этапа:

vue 3 响应式系统原理

Когда значение свойства объекта изменяется, запускается соответствующий установщик.

Функция trigger() в установщике найдет каждое отложение, соответствующее текущему свойству, из таблицы набора зависимостей, и поместит их вeffectsа такжеcomputedEffects(计算属性)очередь, последняяscheduleRun()Выполняйте эффекты один за другим.

Поскольку таблица сбора зависимостей создана, легко найти зависимость, соответствующую атрибуту.Код:

export function trigger (target, operationType, key) {
  // 取得对应的 depsMap
  const depsMap = targetMap.get(target)
  if (depsMap === void 0) {
    return
  }
  // 取得对应的各个 dep
  const effects = new Set()
  if (key !== void 0) {
    const dep = depsMap.get(key)
    dep && dep.forEach(effect => {
      effects.add(effect)
    })
  }
  // 简化版 scheduleRun,挨个执行 effect
  effects.forEach(effect => {
    effect()
  })
}

Код здесь не обрабатывает некоторые особые случаи, такие как длина изменяемого массива, заинтересованные читатели могут проверитьИсходный код, соответствующий vue-next,илиэта статья, и посмотрите, как обрабатываются эти ситуации.

На этом реактивная фаза завершается.

Суммировать

Процесс чтения исходного кода полон трудностей, но в то же время меня часто поражают некоторые идеи реализации Vue, и я многому научился. В соответствии с процессом работы адаптивной системы эта статья разделяет три этапа: «инициализация», «сбор зависимостей» и «отзывчивость», соответственно объясняя, что делает каждый этап, что должно помочь читателям лучше понять его основные идеи. Наконец, адрес склада примера кода статьи прилагается, и заинтересованные читатели могут воспроизвести его самостоятельно:

tiny-reactive