Использование прокси для реализации адаптивных идей в Vue.js 3

Vue.js

Мы знаем, Vue.js 2 черезObject.defineProperty()функция реализации отзывчивости. 5 числа этого месяца Youda выпустил исходный код Vue.js 3, и в сообществе сразу же появилось множество статей о совместном использовании исходного кода. Всем давно известно, что новая версия отзывчивости реализована с помощью Proxy, и теперь мы будем использовать Proxy для реализации базового адаптивного каркаса.

Основание

оProxyБазовые знания, вы можете пойти в MDN, чтобы узнатьПрямая ссылка.

Отзывчивое ядро

Готов к работе

В этой статье основная функция, реализующая отзывчивость, называетсяreactive, эта функция возвращает прокси-результат, который можно активировать, манипулируя возвращенным результатом.

Во-первых, дайте тестовые данные:nameобъект свойства obj, через который мы хотим пройтиreactiveВозвращает новый объект после обработки функцииproxyObj, мы работаем какobjНравится операцияproxyObjобъект.

let obj = {
    name: 'test name'
}

let proxyObj = reactive(obj);

Например: Reactive срабатывает при изменении proxyObj.name.

Второе: установите функцию для имитации реактивного процесса, здесь нет необходимости фактически обновлять DOM. Мы можем настроить функцию, которая может просто вывести строку, указывающую, что необходимо выполнить текущее обновление представления.

// 提示视图需要更新
function trigger() {
    console.log('视图需要更新');
}

выполнитьreactiveфункция

вспомогательная функция

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

Здесь вам нужно создать вспомогательную функцию, чтобы определить, является ли переменная объектом:

function isObject(param) {
    return typeof param === 'object' && param !== null;
}

основная функция

При использовании Proxy вам необходимо определить обработчик прокси-объекта для выполнения прокси-операций над целью Этот объект в основном имеет два метода, а именно get и set, которые запускаются при получении и установке значений свойств соответственно. В то же время внутренняя реализация должна использовать объект Reflect, подробности см. в приведенном ниже коде.

/* 返回一个被代理后的结果,通过操作这个结果可以来实现响应式, 例如视图更新 */
function reactive(target) {
    // 如果是个对象,则返回被代理后的结果,如果不是则直接返回
    if(!isObject(target)) {
        return target;
    }
    
    // 需要定义一个代理对象来对 target 进行代理操作
    // 这个对象主要有两个方法,即 get 和 set
    const handler = {
        get(target, key, receiver) {
            return Reflect.get(target, key, receiver); // 相当于 return target[key]
        },
        set(target, key, value, receiver) {
            trigger();
            return Reflect.set(target, key, value, receiver); // 相当于 target[key] = value
        }
    };

    // 利用 Proxy 来代理这个对象属性
    let observed = new Proxy(target, handler);

    return observed;
}

Теперь мы модифицируемproxyObjАтрибут name обнаружил, что запускается самый простой ответ:

let obj = {
    name: 'jjjs'
}

let proxyObj = reactive(obj);

// 修改 name 属性,发现可以监控到
proxyObj.name = 'new Name';
console.log(proxyObj.name);

Результат выводится:

И может быть отслеживается не иначе не существует атрибутов:

proxyObj.age = 6;
console.log(proxyObj.age);

результат:

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

let array = [1,2,3];
let obArray = reactive(array);
obArray.push(4)

правильноreactiveвыполнение кода, модификация, выводsetкаждый раз в действииkey, чтобы увидеть, кто инициировал его дважды:

function reactive(target) {
    // ...
        set(target, key, value, receiver) {
            trigger();
            console.log(key); // 输出变动的 key
            return Reflect.set(target, key, value, receiver); // 相当于 target[key] = value
        }
    };
    // ...
}

Из приведенного выше рисунка видно, что при мониторинге массива обновление индекса массива будет запущено один раз, а сам массивlengthОбновление также будет запущено, что является причиной вторичного триггера.

Но нам не нужноlengthПредставление обновляется, когда оно обновляется, поэтому логика здесь должна быть изменена: только действие модификации частного свойства запускает обновление представления.

function reactive(target) {
    const handler = {
        // ...
        set(target, key, value, receiver) {
            // 只对私有属性的修改动作触发视图更新
            if(!target.hasOwnProperty(key)) {
                trigger();
                console.log(key);
            }
            return Reflect.set(target, key, value, receiver); // 相当于 target[key] = value
        }
    };
    // ...
}

Когда вам нужно получить вложенные объекты, например:

// 对于嵌套的对象
var obj = {
    name: 'jjjs',
    array: [1,2,3]
}

var proxyObj = reactive(obj);
proxyObj.array.push(4);

В этот момент будет обнаружено, что подсказка о необходимости обновления представления не будет срабатывать, что требует рекурсивной обработки объекта:

function reactive(target) {
// ...
    const handler = {
        get(target, key, receiver) {
            const proxyTarget =  Reflect.get(target, key, receiver); // 相当于获取 target[key]
            if(isObject(target[key])) { // 对于对象进行递归
                return reactive(proxyTarget); // 递归
            }

            return proxyTarget;
        },
        // ...
    };
// ...
}

На данный момент установлено, что мониторинг может выполняться в обычном режиме:

Однако, когда время получит результат прокси, будут случаи нескольких триггеров агента:

function reactive(target) {
// ...
    console.log('走代理');
    
    // 利用 Proxy 来代理这个对象属性
    let observed = new Proxy(target, handler);
    return observed;
}
// 多次获取代理结果
var proxyObj = reactive(obj);
var proxyObj = reactive(obj);
var proxyObj = reactive(obj);
var proxyObj = reactive(obj);

результат:

走代理
走代理
走代理
走代理

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

Поскольку наиболее подходящим целевым контейнером кеша являетсяWeakMap, 这是由于它对于对象的弱引用特性。 правильноWeakMapздесьОзнакомьтесь с его функциями.

Теперь измените код, чтобы добавить объект кеша:

const toProxy = new WeakMap(); // 用来保存代理后的对象

function reactive(target) {
    // ...
    if(toProxy.get(target)) { // 判断对象是否已经被代理了
        return toProxy.get(target);
    }
    // ...
    console.log('走代理');
    // 利用 Proxy 来代理这个对象属性
    let observed = new Proxy(target, handler);

    toProxy.set(target, observed); // 保存已经代理了的对象

    return observed;
}

Теперь обратите внимание на результат приведенного выше кода:

走代理

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

Суммировать

Вышеприведенное использует только десятки строк кода, чтобы сделать минималистскую реализацию прокси для отзывчивости.Хотя это просто, этого достаточно, чтобы понять идею.

Адрес исходного кода:Here

Если есть ошибки, спасибо за указание~