Почему vue3 использует прокси вместо defineProperty

Vue.js

Перед этим мы должны сначала понять основную концепцию Vue.mutable

Будь то vue2 или vue3, в процессе реализации основная концепция оставалась стабильной, а концепция переменных источников данных является ядром для реализации изменения и обновления всего пользовательского интерфейса.

Самый простой способ: инициализировать данные для создания страницы, напрямую изменить исходные данные, чтобы вызвать обновление, и страница будет повторно отображена.

Любой, кто обращает внимание на vue, знает, что прокси используется в vue3 для замены defineProperty.

При использовании vue2 мы часто сталкиваемся с проблемой добавления новых свойств объектаobj.a = 1Его не сможет взломать vue2, вы должны использовать тот, который предоставлен vue2.$setспособ обновления

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

const a = {
    b: 1,
};
Object.defineProperty(a, 'b', {
    set: function() {},
    get: function() {},
});

Когда мы добавляем свойство к объекту, вновь добавленное свойство не перехватывается с помощью defineProperty, хотя новое свойство по-прежнему успешно генерируется для соответствующего объекта, но мы знаем, что vue2 находится через установщик defineProperty, а получатель выполняет перехват данных. вновь добавленные данные не были перехвачены, страница не будет повторно отображаться независимо от того, как она обновляется.

В vue3 использование прокси для прокси данных вообще не вызывает таких проблем.

const p = new Proxy({
    a: 1,
    b: 2,
}, {
    get: function(obj, value) {
        console.log('get', obj, value);
        return Reflect.get(obj, value);
    },
    set: function(obj, prop, value) {
        console.log('set', obj, prop, value);
        return Reflect.set(obj, prop, value);
    },
})

Для прокси данных прокси может отвечать на только что добавленный атрибут.При добавлении атрибута он может отвечать на получение и проксирование текущего объекта.

Как vue3 проксирует через прокси

Прежде всего, вы можете посмотреть на несколько новых основных API в vue3.ref, reactive, effect,computed

реф и реактивный

const normal = ref(0);
const state = reactive({
    a: 1,
    b: 2,
})

Также используется совместимая обработка vue2 в vue3.reactive,Прямо сейчасinstance.data = reactive(data), использовать весь атрибут данныхreactiveпрокси

Мы знаем, что данные в vue2 должны использоватьсяObject.definePerprotyдля угона данных, то вreactive, как он использует прокси для проксирования данных, чтобы быть совместимым со старым методом записи и новым составомApi

ps: Так как в реактиве входящие данные проверяются и проксируются только через прокси, то самое главноеsetа такжеget, так что давайте сразу на базу, ведь мы можем есть горячий тофу на скорую руку

get

Его можно анализировать, будь то vue2 или vue3, основное содержание того, что делается для сбора данных, не будет отличаться.

  1. Получить данные текущего требуемого ключа
  2. зависимая коллекция

Однако для особенности использования прокси в vue3 здесь сделана дополнительная часть совместимости.

  1. Если полученные данные являются объектом, они будут снова использованы на объекте.reactiveВыполнить прокси данных
  2. Если это неглубокий прокси-сервер данных, он будет напрямую возвращать текущие полученные данные.

эффект зависит от коллекции

В дополнение к обычному прокси-серверу данных, vue также имеет соответствующие вычисляемые и наблюдаемые методы, а в vue3 методы watchEffect и вычисляемые напрямую используются для непосредственного создания соответствующего контента.

Их обновление данных и обработка зависимостей зависят от получения текущих данных для сбора зависимостей.

Посмотрите на основной код от начала до конца


// targetMap当前所有代理的数据的一个Map集合
// depsMap当前代理的数据的每一个Key所对应的Map集合
// dep当前代理的数据中的key的对应依赖
// activeEffect当前由effect或者computed生成的数据

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()))
}
if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
}

Если вы просто начнете с этого кода для интерпретации, могут возникнуть некоторые трудности.С другой точки зрения, с точки зрения общего использования vue3, вернитесь к интерпретации этого кода.

setup() {
    const b = reactive({
        c: 1,
        b: 2,
    });
    // effect是vue中的reactivity包直接返回出来的方法
    const a = effect(() => {
        return b.c;
    })
}

Прежде всего, в эффекте используется b, определяемый реактивным.Из поверхностного явления мы можем знать, что при изменении b.c синхронно также будет изменяться a.

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

Что касается того, почему установлено получение зависимости от такого сложного свойства, это связано с тем, что использованиеproxyпричина,proxyПроксируйте весь объект, вы не можете использовать его как vue2Obect.definePropertyНепосредственно выполнить привязку зависимости к текущему полю в геттере, поэтому в vue3 весь объект напрямую используется как карта, ключ каждой карты — это соответствующее свойство, а значение — все объекты, которые зависят от текущего свойства.

set

То же, что и get, с сохранением исходной идеи и режима

  1. установить текущие данные
  2. Публиковать подписанные данные (активировать обновления зависимостей)

В vue3 еще есть некоторые отличия, все-таки это библиотека, которая вытаскивается отдельно

  1. Если эффект вызывается напрямую, он будет изменен непосредственно при изменении обнаруженных данных.
  2. Если вы вызовете watch или watchEffect, он перейдет к собственной схеме планирования vue.

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

Как это сделать на самом деле очень просто

  1. Получить текущие обновленные данные по зависимостям
  2. Группа входит в набор, ожидающий запуска
  3. воплощать в жизнь

Но в нем есть особая обработка.Для атрибута длины массива этот атрибут другой.Далее я расскажу о работе с массивом в vue3.

множество

В vue2 для массива выполняется еще один уровень обработки, который представляет собой базовый метод массива, потому что использованиеObject.definePropertyУ массивов есть естественные недостатки

Конкретные причины очень четко прописаны в документации vue, поэтому подробно описывать их здесь я не буду.

адрес документа

Использование прокси в vue3 отлично решает эту проблему, просто потому, что прокси умеет мониторить изменения массива и делать тест

const a = new Proxy([1,2], {
    get: function(obj, prop) {
        console.log('get', obj, prop);
        return Reflect.get(obj, prop);
    },
    set: function(obj, prop, value) {
        console.log('set', obj, prop, value);
        return Reflect.set(obj, prop, value);
    },
});
a.push(1);

get [1,2] push
get [1,2] length
set [1,2] 2 1
set [1,2, 1] length 3

Когда мы проксируем массив и напрямую вызываем push для вставки новых данных, можно ясно видеть, что и геттер, и сеттер будут вызываться дважды, один из них — вызываемый метод push, а другой — длина массива, который то есть прокси не только обнаружит метод, который мы в данный момент вызываем, но также узнает, изменилась ли длина наших данных.

Увидев это, может возникнуть сомнение, push — это операция над текущим массивом, но в массиве есть какие-то методы, которые вернут новый массив, не будет ли проксировать и вновь сгенерированный массив, вот возьмем splice например

// a= [1,2]
a.splice(0, 1)

get [1,2] push
get [1,2] length
get [1,2] constructor
get [1,2] 0
get [1,2] 1
set [1,2] 0 2
set [2,empty] length 1

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

Теперь вернемся назад и посмотрим на метод триггера vue3.Это обновление зависимостей, запускаемое vue после того, как набор завершен.То же самое делается.В дополнение к обычному выполнению давайте взглянем на оптимизацию для массивов.

// add方法是将当前的依赖项添加进一个等待更新的数组中
else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
} 

Поскольку мы знаем, что при работе с массивом одновременно будет выполняться несколько наборов, то если зависимости будут обновляться каждый раз при наборе, это приведет к потере производительности, поэтому в vue3 только вset lengthбудет вызван, когдаaddметод, а затем выполнить все обновления единообразно

Эпилог

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

Хотя совместимость прокси намного хуже, чем у defineProperty, в vue практически отказались от IE, поэтому, если ваш проект должен работать под ie, откажитесь от опции vue и используйте более низкую версию реакции.

В мобильном терминале такого ограничения по версии в принципе нет.Если версия слишком низкая и прокси использовать нельзя, я считаю, что это можно найти по поиску полифилов.