Эта статья впервые появилась в моем блоге:«Картинка для пояснения адаптивной системы Vue 3.0»
С выпуском Vue 3.0 Pre Alpha мы получили представление о реализации исходного кода. Одной из лучших особенностей Vue является его система реактивности, и мы можем найти соответствующую реализацию в модуле packages/reactivity репозитория. Хотя кода в исходниках не так много, а аналитических статей в интернете много, все же довольно утомительно четко понимать конкретный процесс реализации принципа отзывчивости. После дня исследований и сортировки я обобщил принцип его отзывчивой системы в виде рисунка, и эта статья также будет посвящена этому изображению, чтобы описать конкретный процесс реализации.
Статьи, связанные с кодом, я также загрузил всклад, в сочетании с кодом читать эту статью будет более плавно!
базовый пример
Отзывчивая система Vue 3.0 — это независимый модуль, который можно использовать полностью без Vue, поэтому после того, как мы клонируем исходный код, мы можем отлаживать его непосредственно в модуле packages/reactivity.
- Запуск в корневом каталоге проекта
yarn dev reactivity, затем введитеpackages/reactivityкаталог, чтобы найти выводdist/reactivity.global.jsдокумент. - создать новый
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>
- Откройте файл в браузере и запустите его в консоли
state.count++, вы можете увидеть выводset count to 1.
В приведенном выше примере мы используемreactive()функция положитьoriginОбъект преобразуется в объект Proxystate;использоватьeffect()функция положитьfn()как реактивный обратный вызов. когдаstate.countКогда происходит изменение, оно запускаетсяfn(). Далее мы будем использовать этот пример в сочетании с приведенной выше блок-схемой, чтобы объяснить, как работает эта адаптивная система.
фаза инициализации
На этапе инициализации выполняются две основные операции.
- Пучок
originОбъект преобразуется в реактивный объект Proxy.state. - поставить функцию
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()
}
}
}
На этом этап инициализации завершен. Следующий шаг — самый важный во всей системе — этап сбора зависимостей.
Этап сбора зависимостей
Момент срабатывания этой стадии — когда эффект выполняется немедленно, и его внутренние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 здесь должен выглядеть так:
так,{ 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 и конструирует три эффекта. Взгляните на эффект на консоли:
Эффект ожидаемый, так как же он достигается? Для начала рассмотрим принципиальную схему этого этапа:
Когда значение свойства объекта изменяется, запускается соответствующий установщик.
Функция 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, и я многому научился. В соответствии с процессом работы адаптивной системы эта статья разделяет три этапа: «инициализация», «сбор зависимостей» и «отзывчивость», соответственно объясняя, что делает каждый этап, что должно помочь читателям лучше понять его основные идеи. Наконец, адрес склада примера кода статьи прилагается, и заинтересованные читатели могут воспроизвести его самостоятельно: