Основные продуктовые группы The New York Times вкладывают значительные средства в React.js, и мы создаем совершенно новую веб-платформу для всех наших продуктов. В рамках этого у нас была редкая возможность переосмыслить то, как мы делаем многие вещи, включая получение данных, объединение приложений, рекламу, отслеживание и аналитику.
Традиционно проблемы отслеживания и аналитики в нашем приложении были «закреплены» постфактум. То есть мы часто использовали DOM как источник истины о существовании и взаимодействии различных элементов на странице. Это было не только непросто, но и было очень трудно обойтись без раскрытия несемантических данных, необходимых для просмотра и прикрепления аналитических модулей. В некоторых случаях это было невозможно, и нам приходилось размещать аналитический код в кажущихся случайными местах в нашем приложении.
Мы увидели в этой переплатформенной работе прекрасную возможность «встроить» аналитику и отслеживание в структуру, которую мы строили. Мы хотели отойти от DOM как источника истины и вместо этого использовать сами компоненты, чтобы сигнализировать о взаимодействиях, представляющих интерес. Это также позволило нам использовать саму иерархию приложений для установления часто необходимых событий отслеживания контекстной осведомленности.
TL;DR
Наше решение завершилось разработкой response-tracking, который предоставляет богатый декларативный API для встраивания отслеживающей информации в ваше приложение. Мы открыли исходный код этой библиотеки.
Response-tracking позволил нам уйти от CSS-выбора интересующих элементов и присоединения обработчиков. Вместо этого мы можем украсить сами обработчики соответствующими данными отслеживания.
Как это работает
Вкратце, как работает отслеживание реакции:
- Вы определяете
dispatch()
функцию верхнего уровня, которая определяет, куда идут ваши объекты отслеживания. Если вы его не определите, по умолчанию они будут переведены вwindow.dataLayer[]
- хороший вариант по умолчанию, если вы используете Google Analytics, - но легко переопределить, если вам нужно, чтобы он перешел в другое место, или вам нужно улучшить с помощью некоторого вызова API перед отправив его в GA. - Вы украшаете различные компоненты (страницы, формы, кнопки, ссылки и т. Д.) И любые обработчики или методы жизненного цикла в этих компонентах (onClick, componentDidMount и т. Д.) Только теми данными, которые у вас есть, когда они у вас есть. Это можно использовать как статический объектный литерал или как функцию, возвращающую объектный литерал, и это полезно в тех случаях, когда данные, которые вам нужны, являются функцией реквизита.
- Вот и все! Всякий раз, когда запускается событие (например, вы украсили обработчик кликов), объект отслеживания будет глубоко объединен, начиная с этого компонента на всем пути вверх по иерархии компонентов до верхнего уровня, а затем объединенного объекта. будет передан
dispatch()
.
Это означает, что задачи отслеживания каждого компонента должным образом разделены на этот компонент. Вам не нужно «просачивать» информацию вверх (или вниз) по иерархии компонентов. Это избавляет вас от необходимости беспокоиться о связывании компонентов или обновлении информации отслеживания по мере перемещения компонентов или их рефакторинга.
Пример
Вот как это выглядит на практике (на теоретическом примере страницы входа с упрощенными битами, относящимися к отслеживанию реакции):
/* SignInPage.js */ import React, { Component } from 'react'; import track from 'react-tracking'; import SignInForm from './SignInForm'; import { Header, Footer } from './PageFurniture'; @track({ page: 'sign-in', }) export default class SignInPage extends Component { render() { return ( <div> <Header /> <SignInForm /> <Footer /> </div> ); } }
И форма входа:
/* SignInForm.js */ import React, { Component } from 'react'; import track from 'react-tracking'; @track({ module: 'sign-in', }) export default class SignIn extends Component { @track({ event: 'sign-in-attempt', }) handleSignIn = () => { // API call ... }; @track({ event: 'register-attempt' }) handleRegister = () => { // register form ... } render() { return ( <form> <input type="text" name="username" placeholder="username" /> <input type="password" name="password" placeholder="password" /> <button onClick={this.handleSignIn}>Sign in</button> <button onClick={this.handleRegister}>Register</button> </form> ); } }
Вот как выглядит объект отслеживания, если пользователь, например, нажал «Войти» (действие регистрации будет выглядеть примерно так же):
{ page: 'sign-in', module: 'sign-in', event: 'sign-in-attempt' }
Простой! Это, конечно, при условии, что в приложении нет других данных контекстного отслеживания. На практике одним из распространенных шаблонов является определение некоторых данных отслеживания верхнего уровня в корневой <App />
оболочке для определения глобальных вещей, таких как имя приложения, номер сборки и т. Д. В этом случае эти данные приложения будут частью результирующего отправляемого объекта. .
Что это нам дает
Вот несколько замечаний по поводу этого шаблона.
- Как и в случае с любой чистой компонентной архитектурой,
<SignInForm />
фактически не знает, где он рендерится, однако контекстная информацияpage
появилась автоматически. - Более того, Sign In
<button />
не знает, в каком модуле (или странице) он отображается (особенно полезно, если кнопка была определена в другом файле), он просто знает о событии, которое необходимо отправить при нажатии, это единственное место, где должна быть известна эта информация. - Мы украсили
<SignInPage />
и<SignInForm />
информацией для отслеживания, но мы получили объект отслеживания только тогда, когда произошло событие (которое также было оформлено!), Как и следовало ожидать.
СОВЕТ. Существует распространенный вариант использования события «готовность страницы» при отрисовке страницы. Это также поддерживается путем определения функции
process()
в некотором компоненте верхнего уровня; пример этого рассматривается в следующем разделе.
Простой и расширяемый шаблон
Мы надеемся, что предыдущий пример демонстрирует, насколько простым, но мощным и гибким является этот шаблон. Однако есть надежная поддержка для некоторых довольно сложных случаев использования.
Независимость от платформы аналитики
Функция верхнего уровня dispatch()
позволяет делать все, что вам нужно, с данными отслеживания, которые отправляются по всему приложению. Например, вы можете захотеть улучшить данные с помощью некоторого внутреннего вызова API, прежде чем отправлять их в window.someOtherDataLayer[]
.
Гибкое управление
Функция верхнего уровня process()
позволяет вам «подключиться» к любому декорированному компоненту и вернуть объект для отправки в dispatch()
(возврат ложного значения не сработает). Распространенным вариантом использования является автоматическая отправка события «страница готово» к любым компонентам страницы, например:
process: (data) => (data.page ? { event: 'page-ready' } : null)
При этом проверяется свойство page
объекта отслеживания, и если оно есть, предполагается, что это страница, и возвращается event
«готовая страница», которая затем объединяется с остальными данными отслеживания приложения и передается в dispatch()
.
Расширенные варианты использования
Декоратор @track()
также может принимать функцию. Это полезно, если ваши данные отслеживания основаны на некоторых props
данных.
Он также будет передавать любые аргументы, которые будут отправлены функциям-обработчикам (начиная со второго параметра, поскольку props
всегда является первым). Это полезно для ввода формы, например:
import React, { Component } from 'react'; import track from 'react-tracking'; // In this case, the "status" tracking data // is a function of one of its props (isNew) @track(props => { return { status: props.isNew ? 'new' : 'existing' }; }) export class SomeButton extends Component { // In this case the tracking data depends on // some unknown (until runtime) value (event). @track((props, [event]) => ({ action: 'click', label: event.currentTarget.title || event.currentTarget.textContent, })) handleClick = event => { if (this.props.onClick) { this.props.onClick(event); } }; render() { return ( <button onClick={this.handleClick}> {this.props.children} </button> ); } }
Проверка
В The New York Times мы пошли еще дальше, определив схему отслеживания для проверки. Традиционно контракты с объектами отслеживания координировались вручную, обычно через внутренний документ Google, но мы переходим к более строгой и более структурированной схеме, применяемой программно. Мы используем отличную библиотеку ajv для определения схемы JSON для нашего уровня данных отслеживания.
В нашей среде разработки мы определили dispatch()
для прохождения validate()
функции схемы отслеживания для проверки всех объектов отслеживания и выдачи ошибок при возникновении каких-либо проблем (чтобы уменьшить размер пакета, мы отключаем это в производственной сборке).
/** * Our internal tracking-schema. * The `validator()` function has a process.env.NODE_ENV check * to no-op in production, but validate against JSON schema in dev. */ import { validator } from 'tracking-schema'; import enhanceWithUserData from './userDataApi'; const PAGE_DATA_READY = 'pageDataReady'; // checks dataLayer[] to be available and pushes data to it const pushtoDataLayer = data => { validator(data); // logs errors in dev; no-op in prod (window.dataLayer = window.dataLayer || []).push(data); }; // dispatch() will decide whether to push directly into the DL // or enhance with API-provided data first export const dispatch = data => { if (data.event === PAGE_DATA_READY) { enhanceWithUserData(data).then(pushtoDataLayer); } else { pushtoDataLayer(data); } };
Попытайся!
Мы обнаружили, что отслеживание реакции было легко освоить различными командами, но при этом достаточно сложным, чтобы справиться со всеми нашими сценариями использования до сих пор. Мы будем рады, если вы попробуете это и поделитесь с нами своим мнением. Не стесняйтесь задавать вопросы на нашем Github, если у вас возникнут какие-либо проблемы или возникнут вопросы о том, как делать определенные вещи. И, как всегда, запросы на включение более чем приветствуются!
Спасибо
Большое спасибо кросс-функциональной команде в The Times, которая до сих пор помогла создать отслеживание реакции: Джереми Гайед, Олег Зиньяк, Аневди Абреу, Макс Болдуин, Иван Кравченко и Николь. Барам ».