Многие интерфейсные фреймворки JavaScript, такие как Angular, React и Vue, имеют собственные механизмы реактивности. Поняв отзывчивость и то, как она работает, вы сможете улучшить свои навыки разработки и более эффективно использовать фреймворки JavaScript. существуетвидеоИ в статье ниже мы создаем тот же тип реактивности, который вы видите в исходном коде Vue.
💡Отзывчивая система
Система реактивности Vue выглядит потрясающе, когда вы впервые видите ее. Возьмите это простое приложение Vue:
Как-тоVueпросто знай, еслиpriceизменить, он должен делать три вещи:
обновить страницуpriceценность .
Пересчитайте выражение умноженияprice * quantity, и обновите страницу.
снова вызвать функциюtotalPriceWithTaxи обновить страницу.
Но подождите, вам интересно, как Vue знаетpriceЧто обновлять при его изменении, и как он за всем следит?Это не обычный способ работы с JavaScript.
Большая проблема, которую мы решаем, заключается в том, что вы обычно не программируете таким образом. Например, если я запускаю такой код:
Как вы думаете, что он печатает? Поскольку мы не используем Vue, он напечатает10.
Во Vue мы хотимtotalвместе сpriceилиquantityОбновлять и обновлять. Мы хотим:
К сожалению, JavaScript является процедурным, а не реактивным, поэтому в реальности это не работает. Чтобы достичьtotalВ ответ мы должны использовать JavaScript, чтобы заставить вещи вести себя по-другому.
⚠️ Вопросы
Нам нужно сохранить, как рассчитатьtotal, так что вpriceилиquantityперезапустите его при обновлении,
✅ Решения
Во-первых, нам нужен способ сообщить нашему приложению: «Код, который я собираюсь запустить,сохранить его, мне может понадобиться запустить его в другое время. "Затем начинаем запускать код, еслиpriceилиquantityПеременная обновляется, и сохраненный код запускается снова.
мы можем пройтиrecordфункция, чтобы сделать это, чтобы мы могли запустить его снова.осторожность,мы вtargetАнонимная функция хранится в переменной, а затемrecordфункция. Используя синтаксис стрелки ES6, я также могу написать:этоrecordОпределение функции простое:мы хранимtarget(в нашем случае{ total = price * quantity }), поэтому мы можем запустить его позже, с помощьюreplayфункция для запуска всего, что хранится.Это будет проходить через выполнениеstorageВсе анонимные функции хранятся в массиве.
Затем в коде мы можем:Просто, верно? Если вам нужно прочитать его и попытаться понять его снова, вот полный код, просто для справки, если вы хотите знать, почему, я закодировал его особым образом.
⚠️ Вопросы
Мы можем продолжить запись по мере необходимостиtarget, но есть более надежное решение для расширения нашего приложения. один ответственный за обслуживаниеtargetСписок классов, когда их нужно перезапустить, этиtargetСписок будет уведомлен.
✅ Решение: класс зависимостей
Один из способов решить эту проблему — инкапсулировать это поведение в отдельный класс, зависимый класс, реализующий стандартный шаблон наблюдателя.
Итак, если мы создадим класс JavaScript для управления нашими зависимостями (что ближе к тому, как это обрабатывает Vue), он может выглядеть так:
осторожность, теперь мы храним анонимную функцию какsubscribersвместоstorage. Функция, которую мы сейчас вызываем,dependвместоrecord, теперь мы используемnotifyвместоreplay. Чтобы выполнить этот запуск:Он по-прежнему работает, и теперь код кажется более пригодным для повторного использования. Единственное, что все еще кажется немного странным, это настройка и запуск.target.
⚠️ Вопросы
Мы установим по одному для каждой переменнойDepкласс и красиво инкапсулирует процесс создания анонимных функций, которые должны следить за обновлениями. может быть одинwatcherФункции могут быть разработаны для обработки этого поведения.
Так что не называйте это так:
(это просто код выше)
Мы можем изменить на:
✅ Решение: функция наблюдателя
в нашемWatcherфункцию, мы можем сделать несколько простых вещей:
Как видите,watcherфункция принимаетmyFuncпараметр, установите его как глобальныйtargetсобственность, звонитеdep.depend()Будуtargetдобавить в подписчикиsubscriber,воплощать в жизньtargetфункцию, затем сброситьtargetфункция.
Теперь, когда мы запускаем следующее:
Вам может быть интересно, почему мы сделали target глобальной переменной вместо того, чтобы передать ее в нужную нам функцию. Об этом будет рассказано в конце нашей статьи.
⚠️ Вопросы
у нас есть отдельныйDep class, но на самом деле мы хотим, чтобы каждая переменная имела свою собственнуюDep. Прежде чем продолжить, сделайте данные свойством объекта.
Предположим, что каждое свойство (priceа такжеquantity) иметь свой внутреннийDepДобрый.Теперь, когда мы выполняем:из-за посещенияdata.priceценность я хочуpriceатрибутDepкласс будет храниться вtargetНа него нажимается анонимная функцияsubscriberМассив (позвонивdep.depend()). из-заdata.quantityпосетить, я также надеюсьquantityАтрибутыDepкласс будет храниться вtargetподтолкнуть анонимную функцию к ееsubscriberв массиве.Если у меня есть другая анонимная функция, простоdata.priceдоступ, я хочу, чтобы он просто нажимал наpriceАтрибутыDepДобрый.priceизsubscribersкогда звонитьdep.notify()? я надеюсь вpriceОни вызываются при установке. В конце статьи я хочу иметь возможность зайти в консоль и выполнить:Нам нужен какой-то способ перехвата свойств данных (например,priceилиquantity), поэтому при доступе к нему мы можем сохранитьtargetнашимsubscribersмассив, при изменении которого прогон сохраняется вsubscribersфункции в массиве.
✅ Решение: Object.defineProperty()
нам нужно знатьObject.defineProperty()функция, которая представляет собой простой JavaScript ES5. Это позволяет нам определить для свойствgetterа такжеsetterфункция. прежде чем я покажу вам, какDepПрежде чем использовать его в классе, я покажу вам самое простое использование.
Как вы можете видеть, он регистрирует только две строки. Однако на самом деле это неgetилиsetлюбое значение, так как мы злоупотребляем этой функцией. Давайте добавим его обратно сейчас.get()ожидает вернуть значение,set()Значение все еще необходимо обновить, поэтому давайте добавимinternalValueпеременная для хранения нашего текущегоpriceстоимость.
Поскольку нашgetа такжеsetРаботает нормально, как вы думаете, что будет напечатано на консоли?
Поэтому, когда мыgetа такжеsetзначение, мы можем получать уведомления. С некоторой рекурсией мы можем запустить его для всех элементов в массиве данных, верно?
все сейчасgetterа такжеsetter, мы видим это на консоли.
🛠 Соединяем две идеи вместе
Когда такой фрагмент кода запускается иgetpriceзначение, мы хотимpriceЗапомните эту анонимную функцию(target). Таким образом, еслиpriceизменяется, илиsetявляется новым значением, оно вызовет повторный запуск этой функции, поскольку знает, что эта строка зависит от него.
Get=> Запомните текущую анонимную функцию, когда наше значение изменится, она будет запущена снова.
Set=> запускаем сохраненную анонимную функцию, и наше значение меняется вместе с ней.
или просто нашDep ClassС точки зрения
Price accessed (get)=> звонокdep.depend()чтобы сохранить текущийtarget
Давайте объединим эти две идеи и завершим наш окончательный код.
Теперь посмотрим, что произойдет, когда мы выполним.
Именно то, что мы хотели!priceа такжеquantityДействительно, все откликнулись! всякий раз, когда значениеpriceилиquantityПри обновлении весь код будет перезапущен.
Эта иллюстрация в документах Vue должна начать обретать смысл сейчас.
Вы видите этот красивый фиолетовый круг данныхgetters and settersВсе же? Это должно выглядеть знакомо! Каждый экземпляр компонента имеетwatcherэкземпляр (синий), который начинается сgetter(красная линия) Соберите зависимости. позвони позжеsetter, Так и будетуведомлятьwatcherВызывает повторную визуализацию компонента. Рисунок после аннотации выглядит следующим образом.
Да, разве это не имеет больше смысла сейчас?
Очевидно, что Vue делает это более сложным, но теперь вы знаете основы.
⏪ Итак, что мы узнали?
как создатьDepкласс для сбора зависимостей (depend) и перезапустите все зависимости (notify).
как создатьWatcherпрограммы для управления работающим кодом, которые могут потребоваться в качестве зависимостей (target)Добавлен.
как использоватьObject.defineProperty()Создайтеgetterа такжеsetter.