← Назад к вопросам

Где хранится таймер?

2.0 Middle🔥 111 комментариев
#JavaScript Core#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Где хранится таймер - полный обзор хранилищ

Введение

Этот вопрос проверяет понимание того, где и как сохранять состояние приложения. Таймер может храниться в разных местах в зависимости от требований.

1. Memory (оперативная память) - основной вариант

Для простых таймеров внутри компонента:

import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) return;

    // Таймер хранится в памяти браузера
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    return () => clearInterval(interval); // Очистка при unmount
  }, [isRunning]);

  return (
    <div>
      <p>Время: {seconds} сек</p>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? 'Стоп' : 'Старт'}
      </button>
    </div>
  );
}

Проблема: Таймер теряется при обновлении страницы.

2. Local Storage - сохранение между сессиями

Для стойкого хранения:

import { useState, useEffect } from 'react';

function PersistentTimer() {
  const [seconds, setSeconds] = useState(() => {
    // Читаем из Local Storage при загрузке
    const saved = localStorage.getItem('timerSeconds');
    return saved ? parseInt(saved) : 0;
  });
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    // Сохраняем в Local Storage при каждом изменении
    localStorage.setItem('timerSeconds', seconds.toString());
  }, [seconds]);

  useEffect(() => {
    if (!isRunning) return;

    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

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

  const resetTimer = () => {
    setSeconds(0);
    localStorage.removeItem('timerSeconds');
  };

  return (
    <div>
      <p>Время: {seconds} сек</p>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? 'Стоп' : 'Старт'}
      </button>
      <button onClick={resetTimer}>Сброс</button>
    </div>
  );
}

Проблема: Local Storage хранит текст, не может автоматически продолжить отсчёт при закрытии браузера.

3. Session Storage - временное хранилище сессии

Для сессионного таймера:

function SessionTimer() {
  const [seconds, setSeconds] = useState(() => {
    // Хранится только на время открытой вкладки
    const saved = sessionStorage.getItem('sessionTimer');
    return saved ? parseInt(saved) : 0;
  });

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => {
        const newValue = prev + 1;
        sessionStorage.setItem('sessionTimer', newValue.toString());
        return newValue;
      });
    }, 1000);

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

  return <p>Сессионное время: {seconds} сек</p>;
}

Различие Local vs Session:

АспектLocal StorageSession Storage
Время жизниДо ручного удаленияДо закрытия вкладки
Размер~5-10 MB~5-10 MB
ИспользованиеДанные пользователяВременные данные
ДоступИз других вкладокТолько текущая вкладка

4. IndexedDB - мощное хранилище

Для больших данных и сложного таймера:

function IndexedDBTimer() {
  const [seconds, setSeconds] = useState(0);

  // Инициализация IndexedDB
  const initDB = () => {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('TimerDB', 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains('timers')) {
          db.createObjectStore('timers', { keyPath: 'id' });
        }
      };
    });
  };

  // Сохранение в IndexedDB
  const saveTimer = async (timerData) => {
    const db = await initDB();
    const transaction = db.transaction(['timers'], 'readwrite');
    const store = transaction.objectStore('timers');
    store.put({ id: 'mainTimer', seconds: timerData, timestamp: Date.now() });
  };

  // Загрузка из IndexedDB
  const loadTimer = async () => {
    const db = await initDB();
    const transaction = db.transaction(['timers'], 'readonly');
    const store = transaction.objectStore('timers');
    const request = store.get('mainTimer');

    return new Promise((resolve) => {
      request.onsuccess = () => {
        if (request.result) {
          setSeconds(request.result.seconds);
        }
      };
    });
  };

  useEffect(() => {
    loadTimer();
  }, []);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => {
        saveTimer(prev + 1);
        return prev + 1;
      });
    }, 1000);

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

  return <p>IndexedDB Таймер: {seconds} сек</p>;
}

5. Context API - глобальное состояние

Для доступа из разных компонентов:

import { createContext, useContext, useState, useEffect } from 'react';

const TimerContext = createContext();

export function TimerProvider({ children }) {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) return;

    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

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

  return (
    <TimerContext.Provider value={{ seconds, isRunning, setIsRunning }}>
      {children}
    </TimerContext.Provider>
  );
}

export function useTimer() {
  return useContext(TimerContext);
}

// Использование
function App() {
  return (
    <TimerProvider>
      <TimerDisplay />
      <TimerControls />
    </TimerProvider>
  );
}

function TimerDisplay() {
  const { seconds } = useTimer();
  return <p>Время: {seconds}</p>;
}

function TimerControls() {
  const { isRunning, setIsRunning } = useTimer();
  return (
    <button onClick={() => setIsRunning(!isRunning)}>
      {isRunning ? 'Пауза' : 'Старт'}
    </button>
  );
}

6. Redux/Zustand - управление состоянием

С Zustand (легче Redux):

import { create } from 'zustand';

const useTimerStore = create((set) => ({
  seconds: 0,
  isRunning: false,
  incrementSeconds: () =>
    set((state) => ({ seconds: state.seconds + 1 })),
  toggleRunning: () =>
    set((state) => ({ isRunning: !state.isRunning })),
  reset: () => set({ seconds: 0, isRunning: false })
}));

function TimerComponent() {
  const { seconds, isRunning, toggleRunning, incrementSeconds } = useTimerStore();

  useEffect(() => {
    if (!isRunning) return;
    const interval = setInterval(incrementSeconds, 1000);
    return () => clearInterval(interval);
  }, [isRunning, incrementSeconds]);

  return (
    <div>
      <p>{seconds} сек</p>
      <button onClick={toggleRunning}>
        {isRunning ? 'Пауза' : 'Старт'}
      </button>
    </div>
  );
}

7. Backend (база данных) - синхронизация

Для синхронизации между устройствами:

// Frontend
function SyncedTimer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  // Загрузить таймер с сервера
  useEffect(() => {
    fetchTimerFromServer();
  }, []);

  const fetchTimerFromServer = async () => {
    const response = await fetch('/api/timers/me');
    const data = await response.json();
    setSeconds(data.seconds);
    setIsRunning(data.isRunning);
  };

  // Синхронизировать с сервером
  useEffect(() => {
    if (!isRunning) return;

    const interval = setInterval(async () => {
      const newSeconds = seconds + 1;
      setSeconds(newSeconds);

      // Отправляем на сервер каждые 10 секунд
      if (newSeconds % 10 === 0) {
        await fetch('/api/timers/me', {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ seconds: newSeconds, isRunning: true })
        });
      }
    }, 1000);

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

  return <p>Синхронизированный таймер: {seconds}</p>;
}

Python backend:

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

router = APIRouter()

class Timer(Base):
    __tablename__ = "timers"
    id = Column(String, primary_key=True)
    user_id = Column(String)
    seconds = Column(Integer, default=0)
    is_running = Column(Boolean, default=False)
    updated_at = Column(DateTime, default=datetime.utcnow)

@router.get("/api/timers/me")
async def get_timer(user_id: str, db: Session = Depends(get_db)):
    timer = db.query(Timer).filter(Timer.user_id == user_id).first()
    if not timer:
        timer = Timer(id=str(uuid4()), user_id=user_id)
        db.add(timer)
        db.commit()
    return {"seconds": timer.seconds, "isRunning": timer.is_running}

@router.put("/api/timers/me")
async def update_timer(data: dict, user_id: str, db: Session = Depends(get_db)):
    timer = db.query(Timer).filter(Timer.user_id == user_id).first()
    timer.seconds = data["seconds"]
    timer.is_running = data["isRunning"]
    timer.updated_at = datetime.utcnow()
    db.commit()
    return {"status": "ok"}

Сравнение всех вариантов

МестоПлюсыМинусыКогда использовать
MemoryБыстроТеряется при обновленииПростые таймеры
Local StorageСохраняетсяТолько текст, нет автокупаПользовательские настройки
Session StorageВременноеУдаляется при закрытииСессионные данные
IndexedDBБольшой объёмСложное APIБольшие данные
Context APIГлобальный доступНе сохраняетсяСинхронизация между компонентами
Redux/ZustandЦентрализованноеBoilerplateСложное приложение
Backend БДСинхронизацияТребует сервераСинхронизация между устройствами

Рекомендации на собеседовании

Простой ответ: "Таймер можно хранить в нескольких местах в зависимости от требований:

  1. В памяти компонента (useState) для простых случаев
  2. В Local Storage для стойкого сохранения
  3. На сервере (БД) для синхронизации между устройствами
  4. В Context API если нужен глобальный доступ"

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

Выводы

Выбор места хранения таймера зависит от:

  • Длительности: Сессия, день, постоянно?
  • Масштаба: Один компонент или всё приложение?
  • Синхронизации: Только один браузер или несколько устройств?
  • Производительности: Нужны ли частые обновления?

Для большинства проектов достаточно комбинации React State + Local Storage, для больших проектов добавляется Backend DB для синхронизации.