React и hooks

Работа с датами в интерфейсах React почти всегда связана с форматированием, пересчётом значений при изменении состояния и оптимизацией повторных вычислений. Day.js выступает компактной альтернативой Moment.js, сохраняя цепочный API и расширяемость через плагины, при этом легко встраивается в модель функциональных компонентов.


Базовая модель использования Day.js внутри компонентов

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

import dayjs from "dayjs";

function DateView({ timestamp }) {
  const formatted = dayjs(timestamp).format("YYYY-MM-DD HH:mm");

  return <div>{formatted}</div>;
}

Каждый рендер пересчитывает форматирование. Для простых случаев это допустимо, но при частых обновлениях может потребоваться оптимизация через мемоизацию.


Мемоизация форматирования через useMemo

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

import { useMemo } from "react";
import dayjs from "dayjs";

function DateView({ timestamp }) {
  const formatted = useMemo(() => {
    return dayjs(timestamp).format("DD.MM.YYYY");
  }, [timestamp]);

  return <span>{formatted}</span>;
}

useMemo здесь не изменяет поведение Day.js, но снижает количество вызовов форматирования при неизменном timestamp.


Реактивное обновление времени через useEffect и интервал

Частая задача — отображение «живого» времени, которое обновляется каждую секунду.

import { useEffect, useState } from "react";
import dayjs from "dayjs";

function Clock() {
  const [now, setNow] = useState(dayjs());

  useEffect(() => {
    const id = setInterval(() => {
      setNow(dayjs());
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <div>{now.format("HH:mm:ss")}</div>;
}

Ключевой момент заключается в том, что состояние хранит не строку, а объект Day.js, что упрощает дальнейшие преобразования без повторного парсинга.


Вычисляемые значения времени и зависимые состояния

Day.js часто используется для расчёта разницы между датами в зависимости от состояния компонента.

import { useMemo, useState } from "react";
import dayjs from "dayjs";

function Countdown({ target }) {
  const [tick, setTick] = useState(0);

  const diff = useMemo(() => {
    return dayjs(target).diff(dayjs(), "second");
  }, [target, tick]);

  return <div>{diff}</div>;
}

Обновление tick может инициироваться таймером, создавая реактивный пересчёт разницы.


Инкапсуляция логики в пользовательский хук

Повторяющаяся логика форматирования и обновления времени выносится в кастомный хук.

import { useEffect, useState } from "react";
import dayjs from "dayjs";

function useNow(interval = 1000) {
  const [now, setNow] = useState(dayjs());

  useEffect(() => {
    const id = setInterval(() => {
      setNow(dayjs());
    }, interval);

    return () => clearInterval(id);
  }, [interval]);

  return now;
}

Использование:

function Clock() {
  const now = useNow(1000);

  return <div>{now.format("HH:mm:ss")}</div>;
}

Такая абстракция упрощает повторное использование и отделяет временную логику от UI.


Плагин relativeTime и реактивные интерфейсы

Day.js расширяется через плагины, что особенно важно при создании интерфейсов с относительным временем.

import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";

dayjs.extend(relativeTime);

Использование внутри React:

import { useEffect, useState } from "react";
import dayjs from "dayjs";

function RelativeTime({ date }) {
  const [tick, setTick] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setTick(t => t + 1), 60000);
    return () => clearInterval(id);
  }, []);

  return <div>{dayjs(date).fromNow()}</div>;
}

Обновление раз в минуту достаточно для корректного отображения относительного времени без избыточных ререндеров.


Работа с часовыми поясами через plugin timezone

При необходимости отображения локального времени разных регионов используется плагин timezone.

import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";

dayjs.extend(utc);
dayjs.extend(timezone);

Пример использования в компоненте:

function CityTime({ zone }) {
  const now = dayjs().tz(zone);

  return <div>{now.format("HH:mm")}</div>;
}

При изменении зоны компонент автоматически пересчитывает отображаемое значение без дополнительной логики.


Оптимизация повторных вычислений и избегание лишних ререндеров

Частая ошибка заключается в создании новых объектов Day.js на каждом рендере без необходимости. Это особенно заметно при списках.

function List({ items }) {
  return items.map(item => {
    const date = dayjs(item.createdAt).format("DD.MM.YYYY");
    return <div key={item.id}>{date}</div>;
  });
}

Если данные стабильны, форматирование можно заранее вычислять на уровне получения данных или через мемоизированный селектор.


Интеграция с состоянием формы

Day.js часто используется для работы с датами в формах, где требуется синхронизация строкового ввода и внутреннего представления.

import { useState } from "react";
import dayjs from "dayjs";

function DateInput() {
  const [value, setValue] = useState("");

  const parsed = dayjs(value, "YYYY-MM-DD", true);

  return (
    <input
      value={value}
      onCha nge={(e) => setValue(e.target.value)}
    />
  );
}

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


Поведение в SSR и гидратации

При использовании серверного рендеринга важно учитывать, что Day.js вычисляет дату в момент выполнения. Несовпадение времени между сервером и клиентом может привести к расхождениям.

const formatted = dayjs().format("HH:mm");

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


Композиция нескольких хуков для временных интерфейсов

Сложные интерфейсы часто объединяют несколько временных источников: текущее время, относительное время и форматированные даты.

function useTimeModel(targetDate) {
  const now = useNow(1000);

  const formatted = useMemo(() => {
    return dayjs(targetDate).format("DD MMMM YYYY");
  }, [targetDate]);

  const relative = useMemo(() => {
    return dayjs(targetDate).from(now);
  }, [targetDate, now]);

  return { now, formatted, relative };
}

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


Управление обновлениями через событийную модель

В некоторых случаях вместо интервалов используется внешнее событие обновления, например синхронизация с WebSocket или глобальным таймером приложения.

import { useEffect, useState } from "react";
import dayjs from "dayjs";

function useExternalClock(clockEvent) {
  const [now, setNow] = useState(dayjs());

  useEffect(() => {
    const handler = (t) => setNow(dayjs(t));
    clockEvent.subscribe(handler);

    return () => clockEvent.unsubscribe(handler);
  }, [clockEvent]);

  return now;
}

Такой подход снижает нагрузку на UI и синхронизирует время между компонентами.


Паттерны разделения ответственности

Day.js в React-архитектуре обычно используется по трём направлениям:

  • преобразование входных данных в представление
  • вычисление производных значений времени
  • управление реактивными обновлениями через хуки

Изоляция этих зон через useMemo и кастомные хуки снижает связанность компонентов и упрощает масштабирование временной логики в приложении.