Аналитический антипаттерн: каждая кнопка и каждая форма импортируют хелпер track(...), который тянет Segment SDK, который тянет Amplitude, и всё это ещё за фича-флагом — и в момент, когда захочешь сменить провайдера, поменяется половина кодовой базы. Triggery переворачивает картину: компоненты запускают общий ui:event, один триггер маршрутизирует его в нужное место, а смена провайдера — изменение одного файла.
import { createTrigger } from '@triggery/core';export type UiEvent = { name: string; props?: Readonly<Record<string, unknown>>;};export type Destinations = { segment: boolean; amplitude: boolean; ga4: boolean;};export const analyticsTrigger = createTrigger<{ events: { 'ui:event': UiEvent; }; conditions: { destinations: Destinations; }; actions: { sendToSegment: UiEvent; sendToAmplitude: UiEvent; sendToGa4: UiEvent; };}>({ id: 'analytics-fan-out', events: ['ui:event'], required: ['destinations'], handler({ event, check, actions }) { if (check.is('destinations', d => d.segment)) actions.sendToSegment?.(event.payload); if (check.is('destinations', d => d.amplitude)) actions.sendToAmplitude?.(event.payload); if (check.is('destinations', d => d.ga4)) actions.sendToGa4?.(event.payload); },});
Триггер не делает ничего, кроме маршрутизации. Добавление четвёртой точки — одна запись в схеме, один check.is и один новый файл реактора — никаких изменений в продьюсерах.
Кнопка теперь просто кнопка. Сравни со старым стилем import { track } from './analytics' — продьюсер вообще не подозревает о существовании аналитики, кроме того что запускает общее событие.
src/features/dashboard/DashboardButton.tsx
import { useEvent } from '@triggery/react';import { analyticsTrigger } from '../../triggers/analytics.trigger';export function DashboardButton() { const fireUiEvent = useEvent(analyticsTrigger, 'ui:event'); return ( <button type="button" onClick={() => { fireUiEvent({ name: 'dashboard:cta-clicked', props: { variant: 'hero' } }); // …business logic of the actual click here }} > Try it </button> );}
Одно условие отдаёт, какие точки назначения включены. Можешь подключить это к LaunchDarkly, своему сервису флагов, переключателю в localStorage — что угодно — триггеру всё равно.
src/features/analytics/FlagsProvider.tsx
import { useCondition } from '@triggery/react';import { analyticsTrigger, type Destinations } from '../../triggers/analytics.trigger';export function AnalyticsFlagsProvider({ destinations, children,}: { destinations: Destinations; children: React.ReactNode;}) { useCondition(analyticsTrigger, 'destinations', () => destinations, [destinations]); return <>{children}</>;}
Вызовы аналитики обычно не на критическом пути клика — их хочется убрать с главного потока, но при этом не блокировать рендер. Triggery по умолчанию использует планировщик микротасок, и здесь это верный ответ: обработчик клика возвращается синхронно, React перерендеривает, затем срабатывает триггер.