Галоўная / Артыкулы / Тэкставаныя контракты і Zod Guards для жывага панелі аналізу WebSocket

Тэкставаныя контракты і Zod Guards для жывага панелі аналізу WebSocket

Як стварыць надзеяны панель працы у рэальны час у TypeScript: задаць контракты пакета дадзеных, пераверыць прыемныя звескі через WebSocket за дапамою Zod і запобець дублікацыям з’ўязкаў.

1298 слоў

Запрос на „рэальныя цыфры, праўяцей“ здаецца проблемай прадставлення дадзеных, але на самай справе это прыгожа проблема доверлівасці да дадзеных. Калі метрыкі прыходзяць у вигляде нетыпаванага JSON, калі два канцэнтры называюць адно і тое ж поле рознаім чынам, і калі сокеты паўтараюць падключэнне ў цікле, панель керування выглядае актыўной, але ніхто ёй не верыць. У этам практычным кераванні показана невялікая панель аналітыкі у рэальным часе, створаная на TypeScript, і паказаны прынцыпы, якія робяць ёе надзейной: тыпаваны контракт, верыфікацыя на межы сокета, захаванае падключэнне і спецыяльна простая структура.

Чаму нетыпаваная версія не магла бці доверлівай

Разглянем типавую пачатковую точку: напалоўна завершаны панель адміністрацыі, напісаная на простам JavaScript. Сімптамы знаёмы:

  • Значэнні праходзяць через код у вигляде any, таму рэдагар не можа дапамогчы.
  • Бібліятэка для стварэння графікав прымеўае любую форму, яку з’явілася аправіць сервер.
  • Дзеяны API вяртае user, іншы — users_count; гэта стосуецца падобных рашынкаў.
  • NaN паказваецца ў інтэрфейсе кожны раз, калі якое-небудзь поле не ўсёродна або знаходзіцца ў некоректным формате.
  • Ніякі з гэтых не ўзначайныя багі. Усе яны маюць адну асновную прычыну: няма даговоранай структуры вастаноўкі межа джэрелам дадзеных і інтэрфейсам. Рашэнням є адна правіла, якую можа прыменіць усё калектыва: якшо формат дадзеных не заданы і не перакананы, яны не доходзяць да інтэрфейса.

    Фармаванне панелі керування, якую людзі насправды будуць вядомаць

    Панелі керування з вялікім выглядам і панелі керування, якія ўжытковыя, рэдка калі ёсць адной чызай. Скромная першая версія можа включаць толькі:

    1. Колькість візітараў зараз
    2. Коэффіцыент пераканання за апошнія 24 гадзіны
    3. Найбольш відвідваныя сторункі
    4. Тэчны коеффіцыент бягаў
    5. Індыкатор „пасляльшэ апдэйтавана“, каб перагледчыкі ведалі, што дадзеныя ўсё новыя

    Стак парадоксальна спрямованасць:

    • Next.js з App Router
    • TypeScript у строгім режыме
    • Recharts для графікаў
    • WebSockets для адправкі падтрымкі
    • Zod для пераканання кожнага падатку прытаму, прычыму React яго бачыць

    Мета — не ідеальны продукт. Це сэт цыфр, пра якія каманда перестае спарчвацца.

    Спачатку напісаць дактар даных

    У замяну на запрашэнне JSON і надзею, што ён паспаўсвае, спачатку апісайце точна, чаго чакае UI. Наведаныя нижэй типы пакрываюць генерычны показнік (з яго процэнтам змены і ISO-часовым меткай) і цэлы жывы падатак, які адправляе сокет.

    type DashboardMetric = {
      id: string;
      label: string;
      value: number;
      deltaPercent: number;
      updatedAt: string; // ISO
    };
    type LiveDashboardPayload = {
      visitorsNow: number;
      conversionRate: number;
      topPages: Array<{ path: string; views: number }>;
      errorRate: number;
      metrics: DashboardMetric[];
    };
    

    Этыя типы дакументуюць намер і даюць автодапоўненне, але яны зникаюць пад час выканання. Паведамленне WebSocket — це проста строка, і TypeScript не можа пераканацца, што адправляе сервер. Самэлькі наступны крок мае значэнне.

    Параболка кожнага паведамлення сокета за дапомогою Zod

    Шымат Зода адпаважнае контракт і дадзеяе правілы, якія немагчыма выразіць за дапамогою тыпаў: колькасці не можа быць ад’ёмнымі, колькасць пераглядаў стораніцы павінна быць цэлымі числамі, а шчырыні пераканання і частка падазроў неякосці — дзесятковымі чысларамі з дыапазону 0 і 1. Поле updatedAt павінна быць правамым строкам даты і часу.

    import { z } from "zod";
    const LiveDashboardSchema = z.object({
      visitorsNow: z.number().nonnegative(),
      conversionRate: z.number().min(0).max(1),
      topPages: z.array(
        z.object({
          path: z.string(),
          views: z.number().int().nonnegative(),
        })
      ),
      errorRate: z.number().min(0).max(1),
      metrics: z.array(
        z.object({
          id: z.string(),
          label: z.string(),
          value: z.number(),
          deltaPercent: z.number(),
          updatedAt: z.string().datetime(),
        })
      ),
    });
    

    За такім падходам неправамы пакет больш не спрычынае зламу стораніцы і не прыводзіць да падачы значэння NaN у графік. Такі пакет адхоўваецца, і последнія правамыя даны застаюцца на экране.

    Зберагчы як рукапісныя типы, так і схему, можа выйсці ўтрата яснасці. Частая мера падтрымкі — адносіцца да схемы як да аб’екта правды і гэты тип вылучаць за дапамою z.infer<typeof LiveDashboardSchema>. Таксама пераканайцеся ў версіі вашага Zod: няўялікія выпускі пропонуюць z.iso.datetime() як адпаведную форму пераканання па часу, таму падтвердзіце API за дапамою актуальных інструкцый. Чырвонейша інфармацыя пра аднаработку ўсіх схем у разныя слоі можна знайсці ў выкарыстоўванні адной схемы Zod на фронтэнде і бэкенде.

    Контроль перыядічных падключэнняў і дублікатаванняя слухачаў

    Функцыі рэальнага часу часта выконваюцца некоректна. Няпрыгожая першая версія без канца падключаецца занова, на кожной спробе додае новага обрабоўчыка паведамленняї, накладае апдэйты графіка на старыя і ў канцы канцоў уповольнівае працу браузера.

    Спосаб пры рашэнне гэтай проблемы — спаказваць зв’язок як маленькія машына станоў: idle, пасля connecting, потым live; калі сеть вяртаецца до роботы, стан пераходзіць на reconnecting, а пасля знову на live. Найважнейшыя правіла — гэта тое, што адночасна можа існаваць толькі адны сокет. Функцыя connect, паказаная нижэй, стараецца прытрымвацца гэтага правіла: якщо сокет вялікі ўжо адкрыты чы працуе над адкрыццю, яна негайна вяртаеся. Прыходзячыя паведамленні аналізуюцца за дапамою safeParse, якая вяртае об’ект з рэзультатамі замест таго, каб выклікаць адзінаки; такім чынам некоректныя даны фіксуюцца і прахоўваюцца, а коректныя даны адкладаюць стан машыны станоў.

    let socket: WebSocket | null = null;
    
    function connect() {
      if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
        return;
      }
    
      socket = new WebSocket(process.env.NEXT_PUBLIC_WS_URL!);
    
      socket.onmessage = (event) => {
        const parsed = LiveDashboardSchema.safeParse(JSON.parse(event.data));
        if (!parsed.success) {
          console.warn("Invalid live payload", parsed.error);
          return;
        }
        setDashboard(parsed.data);
      };
    }
    

    Перад запускам у працэвыходным режыме трэба закрыць калькі. Функцыя JSON.parse можа выклікваць адзінакоўныя прычыны пад час обрабаткі не-JSON даных, таму яе трэба абгорнуць у блок try/catch. У прадставленам фрагменте показана захоўчыцкая логіка, але не спосаб пераз’яздоўкі; трэба дадаць обробку сігналу onclose з адкладэнням, каб перерыв у роботе сервера не спрычыніў швайнае циклічнае пераз’яздоўкі. У React трэба закрыць сокет у функцыі чысткі эфекта, каб практычнае перзапусканьне (вакол умовы Strict Mode пад час розработкі, якая дазволяе двойную актывацыю эфектаў) не прыводзіла да вытэкання з’язноў.

    Проектаванне са увагай да пытання «дзе мне спачатку скарыстацца?»

    Існуе спакуса апрацаваць жывы панель керування з градіентамі, светліцячымі карточкамі та багатым калерам. Кращы спосаб пераканання — запытаць заінтересаваных асобаў, куды ў першую чаргу трэба скарыстацца, а потым адключыць усё, што не падтрымае гэты выбор. Хорашыя параметры макета ўключаюць:

    • Ряд з максымум чатырма галоўнымі показнікамі
  • Адзін галерэйны чарт
  • Адзін табел
  • Адзіна лінія статусу, напрыклад Live • updated 2s ago
  • Увод даных таксама дапамагае. Калі кожны паказначык мае вялічыню, заданую заздалегідь, інтэрфейс не можа стварыць спецыяльныя элементы для дадзэйна, які ніхто не задаў. Такія абмежэння прабачваюць чыстасць дизайна.

    Што зазначаюць корыстувальнікі пасля запуску

    Калі такі панель керування выходзіць у працэўную версію, адгукі рэдкая час з’являюцца ў аспекте архітэктуры. Людзі кажуць, што нарэшце вераюць у цыфры, што сторанка больш не замарзае, і ўдивляюцца, што вона справжняя у рэальным часе. Гэта і є справжняя месца панелі керування: не галерэя чартоў, а інструмент, на які можна паспакоўвацца пад час зустрэчаў.

    Ключовыя выводы

    • Уводзіце межы і пераканальвайце кожны зовнішні пакет дадзэйна пад час выканання; сам TypeScript не можа бачыць, што адправляе сервер.
    • Закінчыце режым строгасці; гэта дапамагае ў кожны раз, калі змінюецца код.
    • Спрытнае з’яўленне трэба спрацавляць як машыну станоў, дазволяючы толькі аднае сокета.
    • Адзначыце элементы інтэрфейса, пакуль галоўная сюжетна лінія не стане ясной.
    • Валідзіце прыоритет простага адвізу з правильнымі дадзеннямі над апранутым адвізам з сумневнымі дадзеннямі.

    Якщо вы ствараеце свой першы практычны дашборд, не трэба почынаць з великіх масштабаў. Пачніце з адного пакета дадзеных, перакананага схемай Zod, пакажыце тры цифры разам з часовым пазначэнням свежасці, а сокеты дадзіце толькі пасля таго, як гэтыя адносны элементы будуць стабільныя.

    Спаднёе чытанне