Научите, как построить бескаркасную систему скрытых точек

внешний интерфейс
Научите, как построить бескаркасную систему скрытых точек

Авторы: Лилли Цзян, Айцин Донг

задний план

Система погребенных точек

Вообще говоря, полная система скрытых точек состоит из следующих трех частей:

  • заявление
  • Платформа анализа данных
  • SDK платформы данных

Отчет о скрытых точках — это процесс загрузки событий прикладного уровня на платформу верхнего уровня. Например, на веб-сайте магазина пользователь нажимает кнопку «Избранное». В это время генерируется событие клика, и об этом событии сообщается на платформе анализа данных. Таким образом, соответствующие аналитики данных, менеджеры по продуктам, операторы и другие студенты могут анализировать отчетные данные о событиях на платформе анализа данных, чтобы получить все аспекты, которые можно оптимизировать в приложении. Можно видеть, что отчет о скрытых точках является важной частью качества каждого продукта.

Благодаря приведенному выше описанию мы знаем двух главных героев процесса создания отчетов о скрытых точках: приложение и платформу анализа данных. С точки зрения передовых технологий нам обычно нужна помощь третьей роли, которая является SDK платформы данных.Этот SDK инкапсулирует различные интерфейсы платформы анализа данных, предоставляя нам простые методы для вызова и реализации простой загрузки точки захоронения. .

Два скрытых события

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

  • «Событие страницы»: одно из них является общим «событием страницы», таким как пребывание пользователя и время его активности на определенной странице в приложении. Мы надеемся, что такого рода глобальную скрытую точку можно ввести только один раз при инициализации проекта, и не нуждается в коде.в сопровождении.
  • «Триггерное событие»: другое — это настраиваемое «триггерное событие», такое как нажатие определенной кнопки для запуска определенного процесса. Для этого типа события требуется, чтобы учащиеся переднего плана вручную вводили скрытые точки в коде.

Мы разработали набор встроенных SDK загрузки для этих двух событий соответственно. Далее давайте подробно объясним технические знания этих двух SDK.

SDK для обработки "событий страницы" -monitor-tracer

monitor-tracerЭто внешний SDK, используемый для мониторинга видимой и активной продолжительности страниц и компонентов, а также основной компонент встроенной внешней системы Monitor.

задний план

Чтобы лучше понять использование различных бизнес-функций пользователями, чтобы провести соответствующую оптимизацию и настройку продукта:

  • Для общих веб-приложений нам необходимо вести соответствующую статистику пребывания пользователя и времени активности на определенной странице приложения;

  • Для страницы типа большая/канбан/панель инструментов (как показано на рисунке ниже) мы надеемся дополнительно подсчитать время видимости каждого компонента для пользователя на основе размера страницы, чтобы оптимизировать порядок их расположения и содержание.

    一个 dashboard 类页面

Исходя из вышеуказанных требований, мы разработалиmonitor-tracerSDK, предназначенный для реализации"Страница видна, активное время"а также«Видимая продолжительность компонента»Регистрация.

Глоссарий

  • Страница- Веб-страница открыта в браузере, разные страницы отмечены путемlocation.pathnameразличать;
  • время видимости страницы- Совокупное время просмотра страницы пользователями;
  • время активности страницы- совокупная продолжительность эффективных действий пользователя с мышью, клавиатурой и касаниями на странице;
  • Компонент- Набор элементов DOM, являющихся частью страницы. Страница может содержать несколько компонентов;
  • Видимое время компонента- Совокупное количество времени, в течение которого компонент был виден пользователю.

Его отношение:

  • Активная продолжительность страницы ≤ видимой продолжительности страницы;
  • Продолжительность видимости компонента ≤ продолжительности видимости страницы;
  • Когда страница не видна, она должна быть неактивна, и все компоненты на ней также должны быть невидимы.

Просматриваемость страниц и статистика активного времени

В нашем дизайне для измерения времени пребывания и активности страницы необходимы два важных показателя:

  • видимость

    • visible- Страница находится в области просмотра текущего браузера, а окно браузера не свернуто;
    • invisible- Страница не находится в области просмотра текущего браузера или ее невозможно увидеть, так как браузер свернут.
  • Деятельность

    • active- активность пользователя на веб-странице (например, мышь, клавиатура, прокрутка страницы и т. д.);
    • inactive- У пользователя нет активности на странице.

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

计算页面可见和活跃时长

Получить данные о видимости страницы

Web Lifecycle

В июле 2018 года Google предложил набор рекомендаций для описания жизненного цикла веб-страниц.Page Lifecycle APIСпецификация, этот SDK основан на этой спецификации для отслеживания изменений видимости страницы. В спецификации указано, что в процессе загрузки веб-страницы до ее уничтожения она будет трансформироваться между следующими шестью состояниями жизненного цикла посредством различных событий браузера.

состояние жизненного цикла описывать
active Веб-страница видна и имеет фокус
passive Веб-страница видна, но не в фокусе
hidden Веб-страница невидима, но не зависает в браузере, обычно это происходит, когда пользователь переключается на другую вкладку или сворачивает браузер.
frozen Веб-страница зависает в браузере (некоторые фоновые задачи, такие как таймеры, выборка и т. д., приостанавливаются для экономии ресурсов ЦП)
terminated Веб-страницы выгружаются браузером и очищаются от памяти. Это состояние срабатывает, когда обычный пользователь активно закрывает веб-страницу.
discarded Веб-страница была вынуждена очистить браузер. Обычно вызваны серьезным отсутствием системных ресурсов

Отношения преобразования между состояниями жизненного цикла показаны на следующем рисунке:

生命周期状态转化

Из вышеприведенной информации мы можем нарисовать взаимосвязь между состоянием жизненного цикла страницы и видимым состоянием страницы:

состояние жизненного цикла Видимое состояние
active passive visible
hidden terminated frozen discarded invisible

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

Отслеживание изменений жизненного цикла страницы

При разработке спецификации жизненного цикла страницы команда Google Chrome также разработалаPageLifecycle.jsSDK реализует мониторинг состояния жизненного цикла, описанный в этой спецификации, и совместим со всеми браузерами выше IE 9. Для простоты использования и стабильности мы решили использовать этот SDK для мониторинга жизненного цикла. из-заPageLifecycle.jsОн написан на самом JavaScript, мы добавляем в него определения типов и инкапсулируем для совместимости с TypeScript.@byted-cg/page-lifecycle-typed SDK. PageLifecycle.jsиспользуется следующим образом:

import lifecycleInstance, {
  StateChangeEvent,
} from "@byted-cg/page-lifecycle-typed";

lifecycleInstance.addEventListener("statechange", (event: StateChangeEvent) => {
  switch (event.newState) {
    case "active":
    case "passive":
      // page visible, do something
      break;
    case "hidden":
    case "terminated":
    case "frozen":
      // page invisible, do something else
      break;
  }
});

пройти черезPageLifecycle.js, мы можем контролироватьstatechangeсобытия, чтобы получать уведомления, когда изменяется жизненный цикл страницы и когда состояние жизненного циклаactiveа такжеpassiveотметить страницу какvisibleсостояние, пометьте страницу как состояние жизненного цикла, несколько иноеinvisibleСтатус, обновить последнюю видимую временную метку и накапливать видимое время страницы.

PageLifecycle.jsДефекты

Для наших нужд,PageLifecycle.jsОн имеет следующие два дефекта. Мы также внесли некоторые улучшения для этих двух дефектов.

  • Невозможно отслеживать изменения страниц в одностраничном приложении (SPA)

    В одностраничных приложениях страница обычно полагается на маршрут History или Hash для переключения, сама страница не перезагружается, поэтомуPageLifecycle.jsНе удалось обнаружить переключение страниц. Чтобы справиться с этой ситуацией, мы вручную добавили событие смены маршрута в monitor-tracer (popstate replacestateи др.) мониторинг. Если обнаружится, что маршрут страницы изменился, будет считаться, что текущая страница вошлаterminatedжизненного цикла, чтобы выполнить соответствующую обработку. Логика мониторинга изменений маршрутизации здесь повторно использует логику, которую мы разработали ранее.@byted-cg/puzzle-routerSDK, к которому могут обратиться заинтересованные студенты.

  • не могу пойматьdiscardedЖизненный цикл

    discardedЖизненный цикл происходит, когда страница принудительно очищается браузером. На этом сайте уничтожено и вычищено из памяти, вы не можете пройти ни одно событие снаружи, поэтомуPageLifecycle.jsтоже нельзя отправитьdiscardedмероприятие. Как только это произойдет, это приведет к потере очищенной статистики веб-страницы. Чтобы справиться с этим сценарием,monitor-tracerвойдет на страницуinvisibleсостояние, сохраните существующую статистику продолжительности страницы, используяJSON.stringifyсериализуются и хранятся вlocalStorageсреди. Если страница восстановленаvisibleгосударство,localStorageДанные на странице очищаются, и если страница очищается, при следующем входе на страницуlocalStorageДанные предыдущей страницы, хранящиеся в событии, выталкиваются наружу. Это в наибольшей степени гарантирует, что даже при принудительной очистке страницы ее данные могут быть отправлены без потерь.

Получить данные об активности страницы

По сравнению с видимостью страницы оценка активности страницы более проста. Для непосредственной оценки статуса страницы используются следующие методы:activeещеinactiveВот и все.

activeСтандарт суждения

Прослушивая серию событий браузера, мы можем определить, активен ли пользователь на текущей странице.monitor-tracerОтслеживаются следующие шесть событий:

мероприятие описывать
keydown Запускается, когда пользователь нажимает на клавиатуру
mousedown Запускается, когда пользователь нажимает кнопку мыши
mouseover Запускается, когда пользователь перемещает указатель мыши
touchstart Запускается, когда палец пользователя касается сенсорного экрана (только для устройств с сенсорным экраном).
touchend Запускается, когда палец пользователя покидает сенсорный экран (только для устройств с сенсорным экраном).
scroll Запускается, когда пользователь прокручивает страницу

После наблюдения за вышеуказанными событиямиmonitor-tracerпометит страницу какactiveСтатус и запись текущей метки времени, а также накопление активного времени.

inactiveСтандарт суждения

В следующих двух случаях страница будет помечена какinactiveусловие:

  • По истечении определенного временного порога (по умолчанию 30 секунд, который можно настроить при инициализации SDK) шесть событий, указывающих на активность страницы, не обнаруживаются;
  • Статус страницыinvisible.Потому что если страница не видна пользователю, то она должна быть неактивна.

страница отмечена какinactiveназад,monitor-tracerТекущая отметка времени будет записана, а активная продолжительность будет накоплена.

Статистика видимого времени компонента

Подсчет активной длительности уровня компонента требует двух условий: одно — получить все DOM-элементы, которые необходимо подсчитать, а второе — отслеживать эти DOM-элементы в соответствии с определенными стандартами.

Получить элемент DOM, который нужно посчитать

Мониторинг изменений структуры DOM

Получение элементов DOM требует от нас отслеживания изменений во всей структуре DOM.monitor-tracerиспользовалMutationObserver API, Через этот API можно уведомлять о любых изменениях в DOM, таких как добавление или удаление узлов, изменения атрибутов, изменения текстового содержимого.

Концептуально это очень близко к событию, которое можно понимать как срабатывание при изменении DOM.MutationObserverмероприятие. Однако он существенно отличается от событий: события запускаются синхронно, то есть изменения DOM немедленно вызывают соответствующие события, аMutationObserverОн запускается асинхронно, и изменения DOM не будут инициированы немедленно, но не будут инициированы до тех пор, пока не будут завершены все текущие операции DOM.

Этот дизайн предназначен для работы с характеристиками частых изменений DOM и сохранения производительности. Например, если вы вставите 1000 последовательных<p></p>тег, он будет непрерывно запускать 1000 событий вставки и выполнять функцию обратного вызова для каждого события, что может привести к зависанию браузера. а такжеMutationObserverСовершенно другой, он срабатывает только после того, как будет вставлено 1000 тегов, и только один раз.

MutationObserverИспользование API выглядит следующим образом:

const observer = new MutationObserver(function (mutations, observer) {
  mutations.forEach(function (mutation) {
    console.log(mutation.target); // target: 发生变动的 DOM 节点
  });
});

observer.observe(document.documentElement, {
  childList: true, //子节点的变动(指新增,删除或者更改)
  attributes: true, // 属性的变动
  characterData: true, // 节点内容或节点文本的变动
  subtree: true, // 表示是否将该观察器应用于该节点的所有后代节点
  attributeOldValue: false, // 表示观察 attributes 变动时,是否需要记录变动前的属性值
  characterDataOldValue: false, // 表示观察 characterData 变动时,是否需要记录变动前的值。
  attributeFilter: false, // 表示需要观察的特定属性,比如['class','src']
});

observer.disconnect(); // 用来停止观察。调用该方法后,DOM 再发生变动则不会触发观察器

Отметьте элемент, который необходимо контролировать

Чтобы найти элементы для прослушивания среди множества элементов DOM, нам нужен способ пометить эти элементы.monitor-tracerSDK предусматривает, что если компоненту необходимо подсчитывать активное время, он должен добавитьmonitor-pvилиdata-monitor-pvАтрибуты. В использованииMutationObserverПри сканировании изменений DOMmonitor-tracerЭлементы DOM с этими двумя свойствами будут собраны в массив для прослушивания. Например, следующие два компонента:

<div monitor-pv='{ "event": "component_one_pv", "params": { ... } }'>
  Component One
</div>
<div>Component Two</div>

Компонент один, потому что добавлениеmonitor-pvАтрибуты будут записываться и учитывать видимую продолжительность. Второй компонент - нет.

babel-plugin-tracerплагин

Если компонент, который необходимо отслеживать, написан в какой-либо библиотеке компонентов, такой как Ant Design или ByDesign, то добавьтеmonitor-pvилиdata-monitor-pvТакие пользовательские атрибуты могут быть отфильтрованы самим компонентом и не будут отображаться в окончательно сгенерированном элементе DOM, в результате чего мониторинг компонента не будет работать. Для решения аналогичной задачи мы разработали@byted-cg/babel-plugin-tracerПлагин Бабель. Этот плагин будет искать добавленныеmonitor-pvкомпонент атрибута и обернуть пользовательский<monitor></monitor>Этикетка. Например:

import { Card } from 'antd';

const Component = () => {
  return <Card monitor-pv={{ event: "component_one_pv", params: { ... } }}>HAHA</Card>
}

Если плагин не добавлен, окончательный сгенерированный DOM будет следующим:

<div class="ant-card">
  <!-- ... Ant Design Card 组件 -->
</div>

видимыйmonitor-pvАтрибуты исчезли после фильтрации компонентов.

После установки плагина babel окончательная скомпилированная структура DOM выглядит так:

<monitor
  is="custom"
  data-monitor-pv='{ "event": "component_one_pv", "params": { ... } }'
>
  <div class="ant-card">
    <!-- ... Ant Design Card 组件 -->
  </div>
</monitor>

monitor-pvсвойства сохраняются и плагин автоматически добавляет ихdata-префикс, если React 16 поддерживает толькоdata-Проблема с пользовательским атрибутом в начале, при использовании входящего объектаJSON.stringifyПреобразовано в единственный поддерживаемый атрибут элемента DOMstringТипы.

Поскольку у пользовательского тега нет стиля, перенос тега не повлияет на стиль исходного компонента.monitor-tracerПосле сканирования элемента DOM SDK собирает все<monitor></monitor>информацию об элементе в теге и контролировать элемент, который он обертывает.

Определение видимости элемента DOM

Оценку видимости компонента можно разделить на три измерения:

  • Находится ли компонент в окне просмотра браузера — используйтеIntersectionObserver APIсудить;
  • Видны ли стили компонентов — на основе CSS элементаdisplay visibilityа такжеopacityоценка атрибута стиля;
  • Видна ли страница — на основе видимости страницы.

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

Здесь мы используемIntersectionObserver API, Этот API предоставляет способ асинхронного обнаружения целевого элемента и элементов-предков илиviewportСпособы изменения ситуации на перекрестке.

В прошлом при обнаружении пересечений часто использовались прослушиватели событий, которые нужно было часто вызывать.Element.getBoundingClientRectметод для получения информации о границах связанного элемента. Мониторинг событий и звонкиElement.getBoundingClientRectВсе выполняются в основном потоке, поэтому частые запуски и вызовы могут вызвать проблемы с производительностью. Этот метод обнаружения чрезвычайно странный и неэлегантный.

а такжеIntersectionObserverAPI регистрирует функцию обратного вызова, которая запускается и выполняется всякий раз, когда отслеживаемый элемент входит или выходит из другого элемента (или области просмотра) или когда изменяется размер пересечения двух элементов. Таким образом, главному потоку нашего веб-сайта не нужно усердно следить за пересечением элементов, а браузер сам оптимизирует управление пересечением элементов.

IntersectionObserverИспользование API выглядит следующим образом:

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach(function (entry) {
      /**
     entry.boundingClientRect // 目标元素的矩形区域的信息
     entry.intersectionRatio // 目标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,完全可见时为 1,完全不可见时小于等于 0
     entry.intersectionRect // 目标元素与视口(或根元素)的交叉区域的信息
     entry.isIntersecting // 标示元素是已转换为相交状态 (true) 还是已脱离相交状态 (false)
     entry.rootBounds // 根元素的矩形区域的信息, getBoundingClientRect 方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null
     entry.target // 被观察的目标元素,是一个 DOM 节点对象
     entry.time // 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
     **/
    });
  },
  {
    threshold: [0, 0.25, 0.5, 0.75, 1], //该属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为 [0],即交叉比例 (intersectionRatio) 达到 0 时触发回调函数
  }
);

observer.observe(document.getElementById("img")); // 开始监听一个目标元素

observer.disconnect(); // 停止全部监听工作

Если компонент пересекает окно просмотра с коэффициентом меньше определенного значения (по умолчанию 0,25), то компонент помечается какinvisibleИ наоборот, если масштаб больше определенного значения (по умолчанию 0,75), то компонент будет помечен какvisible.

Определите, видны ли стили CSS компонента

Если стиль CSS элемента установлен наvisibility: hiddenилиopacity: 0, то он невидим для пользователя, даже если он пересекает окно просмотра в масштабе 1. Поэтому нам нужно дополнительно определить, видимо ли свойство CSS целевого элемента.

Если для стиля компонента установлено одно из следующих значений, он помечается какinvisible.

  • visibility: hidden
  • display: none
  • opacity: 0

Определить, видна ли страница

Когда страница не видна, все компоненты естественным образом невидимы, поэтому страницаinvisibleсостояние,monitor-tracerтакже пометит состояние всех компонентов, подлежащих мониторингу, какinvisible.

SDK, который обрабатывает «инициированные события» —monitor

monitorПозиционирование SDK

Единый метод отчетности SDK платформы данных не может соответствовать конечной цели чистого кода в нашей разработке.

SDK платформы данных часто предоставляет только функциональный метод для сообщения о скрытых точках.Хотя он может удовлетворить наши повседневные потребности в разработке, он не может решить две основные болевые точки, когда мы пишем скрытый код:

  • Вы можете сообщать о скрытых точках только одну за другой.
  • Сочетание скрытой логики и бизнес-логики

Мы надеемся, что встроенный код можно будет легко добавлять, изменять и удалять, и он не повлияет на бизнес-код. Поэтому мы разрабатываем нечувствительные к фреймворку фреймворки на основе TypeScript.monitorSDK. Он поддерживает загрузку нескольких скрытых точек одну за другой и принимает функции, которые возвращают скрытые точки и сообщают об их возвращаемых значениях; он предоставляет три способа внедрения скрытых точек, охватывающих все сценарии и полностью отделяющих скрытые точки от бизнес-кода.

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

埋点上报流程

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

справаmonitorПосле предварительного понимания эта статья в основном объяснитmonitorКак разделить бизнес-логику и логику скрытых точек с помощью следующих трех методов внедрения скрытых точек.

Давайте взглянемmonitorа такжеmonitor-tracerСпецифический технический дизайн SDK и методы реализации.

Три метода инжекции в заглубленные точки

как императив

monitorПредоставляет метод инструкции класса для внедрения скрытых точек. Например, в следующем коде используетсяmonitor-clickДирективы вводят скрытые точки. Когда эта кнопка нажата (щелчок),monitor-clickСоответствующее значение, то есть событие, будет сообщено.

// 指令式埋点示例
<Button
  monitor-click={JSON.stringify({
    type: "func_operation",
    params: { value: 3 },
  })}
>
  Click Me
</Button>

Как это достигается? Зачем добавлять только один к компонентуmonitor-clickАтрибуты,monitorБудет ли он сообщать о скрытой точке при нажатии этой кнопки?

Реализация и принцип

фактически,monitorКогда SDK инициализируется, он выдаст текущийdocumentОбъект плюс серия прослушивателей событий, прослушивающихhover click input focusи т.д. события. Когда слушатель уволен,monitorвызовет событие изtargetОбъект запускается и проходит уровень за уровнем, чтобы увидеть, есть ли у текущего элемента инструкция, соответствующая этому событию.Если да, сообщайте об этом событии до тех пор, пока не встретится узел элемента без инструкции события. На следующей диаграмме показан процесс создания отчетов о скрытых точках императивного типа:

类指令上报流程

Поэтапный процесс отчетности

Возьмите следующий код в качестве примера, когда курсор наводится наButtonчас,documentпрослушиватели, установленные на объектеhoverФункция события выполнена. Эта функция перваяevent.targetкоторыйButtonузнать, есть лиhoverДирективы, связанные с событиями (т.е. свойства).Buttonимеютmonitor-hoverЭта команда, в это время функция загружает событие, соответствующее этой команде, то есть{ type: 'func_operation', params: { value: 1 }}.

Далее функция поднимается на один уровень вверх, кButtonродительский элементdiv, повторяя описанный выше процесс, он находитdata-monitor-hoverЭта команда также сообщила о соответствующем событии скрытой точки. и прибылsectionХотя этот слой имеетdata-monitor-clickкоманда, но эта команда невернаhoverСобытие откликается, следовательно, процесс пошагового отчёта о закопанных точках закончен.

// 指令式埋点实现逐级上报
<section
        data-monitor-click={JSON.stringify({
        type: 'func_operation',
params: { value: 3 },
})}
>
<div
        data-monitor-hover={JSON.stringify({
        type: 'func_operation',
params: { value: 2 },
})}
>
<Button
        monitor-hover={JSON.stringify({
        type: 'func_operation',
params: { value: 1 },
})}
>
Click Me
</Button>
</div>
</section>

Внедрение скрытых точек в инструкции класса подходит для простого создания отчетов о скрытых точках и четко отделено от бизнес-кода. Но если нам нужно обработать сообщенные данные перед сообщением о событии, то этот метод не может быть удовлетворен. Кроме того, не все сценарии можно охватить событиями DOM. Если я хочу сообщить о скрытой точке, когда пользователь вводит определенное значение в поле поиска, мне нужно проанализировать значение, введенное пользователем, а не в поле поиска.inputО закопанных точках сообщается каждый раз, когда запускается событие.

декоратор

Декоратор — это, по сути, функция более высокого порядка. Он принимает функцию и возвращает другую декорированную функцию. Поэтому мы, естественно, думаем об использовании декораторов для внедрения логики скрытых точек в бизнес-функции, которые не только реализуют отделение скрытых точек от бизнес-кода, но и адаптируются к сложным сценариям скрытых точек.

Код ниже использует@monitorBeforeмодификатор.@monitorBeforeВозвращаемое значение полученной функции является событием, о котором необходимо сообщить. существуетhandleSearchКогда функция вызывается,monitorСначала сообщит о событии закопанной точки, а затем выполнитhandleSearchЛогика функции.

// @monitorBefore 使用示例
@monitorBefore((value: string) => ({
    type: 'func_operation',
    params: { keyword: value },
}))
handleSearch() {
    console.log(
        '[Decorators Demo]: this should happen AFTER a monitor event is sent.',
    );
}

return (
    <AutoComplete
        onSearch={handleSearch}
/>
)

от@readonlyПонять, как работают декораторы

Как декоратор реализует интеграцию логики скрытых точек и бизнес-логики? Подробнее читайте в нашем@monitorBeforeПеред этим начнем с обычного декоратора@readonlyДавайте начнем.

Декораторы применяются к одному члену класса, включая свойства класса, методы, геттеры и сеттеры.При вызове функция декоратора получает 3 параметра:

  • target- класс, в котором находится декоратор
  • name- имя декорируемой функции
  • descriptor- Дескриптор атрибута украшенной функции
// @readonly装饰器的代码实现
readonly = (target, name, descriptor) => {
  console.log(descriptor);
  descriptor.writable = false;
  return descriptor;
};

Приведенный выше код проходитconsole.logРезультат:

代码输出结果

с нашим общим@readonlyНапример, способ его реализации такой, как указано выше. Выйдя из системы в приведенном выше кодеdescriptor, мы узнаем, чтоdescriptorСвойства:

  • writable- Может ли декорированная функция быть изменена оператором присваивания;
  • enumerable- Появляется ли декорированная функция в свойстве перечисления объекта;
  • configurable- Можно ли изменить или удалить из объекта дескриптор декорируемой функции;
  • value- Значение декорированной функции, то есть соответствующий ей функциональный объект.

видимый,@readonlyдекоратор будетdescriptorизwritableсвойство установлено наfalseи возвращает этоdescriptor, он успешно устанавливает для члена класса, который он украшает, доступ только для чтения.

Мы используем следующим образом@readonlyДекоратор:

class Example {
  @readonly
  a = 10;

  @readonly
  b() {}
}

@monitorBeforeреализация

@monitorBeforeдекоратор, чем@readonlyЭто немного сложнее: как он объединяет логику скрытой точки с бизнес-логикой для создания новой функции?

Во-первых, давайте взглянем на следующий код.monitorBeforeПолучить скрытое событиеeventв качестве параметра и возвращает функцию. Параметры возвращаемой функции такие же, как упомянутые выше.@readonlyПолученные параметры совпадают.

// monitorBefore 函数源代码
monitorBefore = (event: MonitorEvent) => {
  return (target: object, name: string, descriptor: object) =>
    this.defineDecorator(event, descriptor, this.before);
};
// @monitorBefore 使用方式
@monitorBefore((value: string) => ({
    type: 'func_operation',
    params: { keyword: value },
}))
handleSearch() {
    console.log(
        '[Decorators Demo]: this should happen AFTER a monitor event is sent.',
    );
}

return (
    <AutoComplete
        onSearch={handleSearch}
/>
)

Во время компиляции@monitorBeforeполучилeventпараметры и возвращает следующую функцию:

f = (target: object, name: string, descriptor: object) =>
  this.defineDecorator(event, descriptor, this.before);

Затем компилятор вызывает функциюf, передать текущий класс, декорированное имя функции и дескриптор ее атрибута вf, функцияfвернутьthis.defineDecorator(event, descriptor, this.before)будет проанализирован как новыйdescriptorобъект, егоvalueбудет вызываться во время выполнения, то есть он будет вызываться во время выполненияonSearchВызывается при срабатывании.

Теперь давайте подробно интерпретируемdefineDecoratorКак изменяется функция для создания новогоdescriptorда.

// defineDecorator 函数源代码
before = (event: MonitorEvent, fn: () => any) => {
  const that = this;
  return function (this: any) {
    const _event = that.evalEvent(event)(...arguments);
    that.sendEvent(_event);
    return fn.apply(this, arguments);
  };
};

defineDecorator = (
  event: MonitorEvent,
  descriptor: any,
  decorator: (event: MonitorEvent, fn: () => any) => any
) => {
  if (isFunction(event) || isObject(event) || isArray(event)) {
    const wrapperFn = decorator(event, descriptor.value);

    function composedFn(this: any) {
      return wrapperFn.apply(this, arguments);
    }

    set(descriptor, "value", composedFn);
    return descriptor;
  } else {
    console.error(
      `[Monitor SDK @${decorator}] the event argument be an object, an array or a function.`
    );
  }
};

monitorBefore = (event: MonitorEvent) => {
  return (target: object, name: string, descriptor: object) =>
    this.defineDecorator(event, descriptor, this.before);
};

defineDecoratorФункция принимает три параметра:

  • event- Закопанные точки, о которых необходимо сообщить;
  • descriptor- дескриптор атрибута декорированной функции;
  • decorator- функция высшего порядка. Он получает событие отслеживания и обратный вызов, возвращает функцию отслеживания отслеживания, а затем выполняет обратный вызов.

decoratorСначала возвращает функциюwrapperFn, Когда звонили,wrapperFnСначала сообщит о скрытой точке, а затем выполнитdescriptor.valueЛогика, то есть декорированная функция.

существуетdefineDecoratorизcomposedFn, мы используемwrapperFn.apply(this, arguments)Прозрачно передавать параметры, переданные при вызове декорированной функции, вwrapperFn.

Наконец, мы будемcomposedFnустановить какdescriptor.value, Таким образом, мы успешно создали новую функцию, которая сочетает в себе логику скрытых точек и бизнес-логику.

То, как декоратор спрятан и внедрен, очень чисто и его можно четко отличить от бизнес-кода. Будь то добавление, изменение или удаление скрытых точек, не нужно беспокоиться об изменениях в бизнес-коде. Но его ограничения также очевидны, декораторы можно использовать только для компонентов класса, и теперь наши часто используемые функциональные компоненты не могут использовать декораторы.

Реагировать на хуки

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

// useMonitor 源代码
useMonitor = (fn: () => any, event: MonitorEvent) => {
  if (!event) return fn;
  const that = this;

  return function (this: any) {
    const _event = that.evalEvent(event)(...arguments);
    that.sendEvent(_event);

    return fn.apply(this, arguments);
  };
};

useMonitorРеализация относительно проста, это просто функция более высокого порядка, в отличие от декораторов, которые требуют разбора синтаксиса. Он возвращает функцию и при вызове сначала загружает событие скрытой точки, а затем выполняет бизнес-логику. Он используется следующим образом:

// useMonitor 使用示例
const Example = (props: object) => {
  const handleChange = useMonitor(
    // 业务逻辑
    (value: string) => {
      console.log("The user entered", value);
    },
    // 埋点逻辑
    (value: string) => {
      return {
        type: "func_operation",
        params: { value },
      };
    }
  );

  return <Search onSearch={handleChange} />;
};

резюме

Вышеуказанные три метода захоронения точек охватывают все сценарии использования. Используете ли вы React, Vue или собственный JavaScript, используете ли вы компоненты класса или функциональные компоненты, требует ли ваше встраивание сложной предварительной логики,monitorВсе пакеты SDK предоставляют методы использования, соответствующие вашему сценарию.

стек технологий

  • Язык программирования -TypeScript
  • Монитор жизненного цикла веб-страницы —PageLifecycle.js
  • Мониторинг событий, диспетчеризация -wolfy87-eventemitter
  • Монитор изменения структуры DOM -MutationObserver API
  • Элемент DOM и прослушиватель перекрестного состояния окна просмотра -IntersectionObserver API

Кредиты разработчиков

  • monitor SDK - [Lilly Jiang]
  • monitor-tracer SDK
    • Просматриваемая и активная страница, плагин babel - [Aiqing Dong]
    • Компонент «Видимое время» — [Юлин Чен]