Глобальное состояние в приложениях - global state

28 января 2026
Автор

Олег Марков

Введение

Глобальное состояние (global state) — это данные, к которым может обратиться значительная часть приложения напрямую, без явной передачи через аргументы функций или параметры компонентов.

Проще говоря, если у вас есть переменная или объект, к которому «дотягиваются» отовсюду, — это и есть глобальное состояние.

Глобальное состояние может жить:

  • В глобальных переменных языка.
  • В синглтонах.
  • В централизованных стор-объектах (Redux store, Vuex store, глобальный Context в React).
  • В статических полях классов (в ООП-языках).

С одной стороны, глобальное состояние удобно: вы один раз где-то что-то сохранили — и можете использовать это в любом месте приложения. С другой — из-за него растет связанность кода, усложняются тесты и отладка, появляются скрытые зависимости.

В этой статье вы увидите:

  • Что именно считается глобальным состоянием и чем оно отличается от локального.
  • Какие проблемы возникают при чрезмерном использовании global state.
  • В каких случаях глобальное состояние действительно оправдано.
  • Какие есть шаблоны и инструменты для управления глобальным состоянием в разных типах приложений.
  • Как уменьшить вред глобального состояния, если без него не обойтись.

Я буду периодически показывать вам код на JavaScript/TypeScript и немного на Go и других языках, чтобы вы могли увидеть общие принципы, а не привязку к конкретному стеку.

Что такое глобальное состояние

Локальное vs глобальное состояние

Сначала давайте четко разделим, о чем речь.

Локальное состояние:

  • Живет внутри функции, метода, компонента или модуля.
  • Доступно только в ограниченной области видимости.
  • Передается дальше только явно — через параметры и возвращаемые значения.
  • Пример: let count = 0 внутри функции, useState в отдельном React-компоненте, локальная переменная в Go-функции.

Глобальное состояние:

  • Доступно из многих (часто любых) частей программы.
  • Как правило, не передается явно — достаточно просто «импортировать» или обратиться к нему по имени.
  • Меняется из разных мест, часто в разное время.
  • Пример: глобальный Redux store, синглтон Config, пакетный уровень переменных в Go.

Смотрите, как это выглядит в простом примере на JavaScript.

// Глобальное состояние приложения
// Здесь мы объявляем объект, который может изменить любой модуль
const appState = {
  user: null,
  theme: 'light',
};

// Любой модуль может прочитать и изменить это состояние
function login(username) {
  // Здесь мы мутируем глобальный объект состояния
  appState.user = { name: username };
}

function logout() {
  // Здесь мы тоже меняем глобальное состояние
  appState.user = null;
}

function getCurrentUser() {
  // Здесь мы читаем глобальное состояние
  return appState.user;
}

Здесь appState — типичный пример глобального состояния. Никакие функции не получают его как аргумент, все обращаются к одной и той же переменной.

Для сравнения, вот вариант с локальным состоянием:

// Здесь мы создаем функцию, которая инкапсулирует состояние
function createAppState() {
  // Это состояние доступно только внутри замыкания
  let user = null;
  let theme = 'light';

  // Мы возвращаем методы, которые управляют этим состоянием
  return {
    login(username) {
      // Здесь мы изменяем локальное состояние
      user = { name: username };
    },
    logout() {
      user = null;
    },
    getCurrentUser() {
      return user;
    },
    getTheme() {
      return theme;
    },
    setTheme(newTheme) {
      theme = newTheme;
    },
  };
}

// Здесь мы создаем конкретный экземпляр состояния
const appState = createAppState();

// Дальше appState можно передать в другие части системы явно

Обратите внимание, что во втором примере состояние по-прежнему единое, но оно не глобальное в языковом смысле: его нужно передать туда, где оно нужно. Это уже уменьшает связанность.

Формы глобального состояния

Теперь давайте разберем, в каких формах вы чаще всего встретите global state.

Глобальные переменные языка

В Go:

package config

// Здесь мы объявляем глобальную переменную на уровне пакета
var AppConfig *Config

type Config struct {
    DBURL string
}

// Init загружает конфигурацию один раз
func Init() {
    // Здесь мы инициализируем глобальную переменную
    AppConfig = &Config{
        DBURL: "postgres://localhost:5432/app",
    }
}

Любой пакет, который импортирует config, может прочитать config.AppConfig. Это удобно, но сильно связывает код с глобальной точкой доступа.

В JavaScript (Node.js):

// Здесь мы записываем в глобальный объект Node.js
global.cache = new Map(); // Глобальный кэш

function getFromCache(key) {
  // Здесь мы обращаемся к глобальному кэшу напрямую
  return global.cache.get(key);
}

Синглтоны (Singleton)

Синглтон — это паттерн, который ограничивает создание класса одним экземпляром и предоставляет глобальную точку доступа к нему.

На TypeScript:

// Здесь мы описываем синглтон для хранения настроек
class Settings {
  private static instance: Settings | null = null;
  private theme: 'light' | 'dark' = 'light';

  // Прячем конструктор, чтобы нельзя было создать объект напрямую
  private constructor() {}

  // Здесь мы даем глобальную точку доступа к единственному экземпляру
  static getInstance(): Settings {
    if (this.instance === null) {
      this.instance = new Settings();
    }
    return this.instance;
  }

  // Геттер и сеттер для состояния
  getTheme() {
    return this.theme;
  }

  setTheme(theme: 'light' | 'dark') {
    this.theme = theme;
  }
}

// Использование
// Мы получаем глобальный доступ к одному и тому же объекту
const settings = Settings.getInstance();
settings.setTheme('dark');

Синглтон часто выглядит как «красивый» способ сделать глобальное состояние, но по сути это тот же global state, только обернутый в класс.

Централизованные сторы в фронтенде

В современных фронтенд-фреймворках глобальное состояние — это обычно стор:

  • React — Redux, Zustand, MobX, глобальный Context.
  • Vue — Vuex, Pinia.
  • Angular — NgRx, Akita.

Например, Redux-стор:

// Здесь мы описываем срез глобального состояния
interface RootState {
  user: { name: string } | null;
  theme: 'light' | 'dark';
}

// Здесь мы создаем Redux store
const store = configureStore<RootState>({
  reducer: rootReducer,
});

// Любой компонент, подключенный к store, может читать и изменять это состояние

Технически стор — это тоже глобальное состояние, но с четкими правилами:

  • Только через actions.
  • Только через редьюсеры.
  • Без прямой мутации.

Явное и неявное глобальное состояние

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

  • Явное глобальное состояние — вы прямо видите, где оно объявлено и как к нему обращаются. Например, const store = ... в корне приложения, config.AppConfig в Go.
  • Неявное глобальное состояние — оно живет «где-то внутри», например:
    • В базе данных.
    • В файловой системе.
    • В окружении (ENV-переменные).
    • В сессии пользователя.
    • В кешах фреймворка.

Даже если у вас нет прямой глобальной переменной, сама по себе внешняя система (БД, Redis, внешнее API) тоже выступает как глобальное состояние приложения. Это важно помнить, когда вы тестируете и отлаживаете код.

Зачем используют глобальное состояние

Глобальное состояние не случайно так популярно. Давайте разберем его сильные стороны.

Централизованные общие данные

Во многих приложениях есть данные, которые логично хранить в одном месте:

  • Текущий пользователь и его права.
  • Тема оформления и настройки интерфейса.
  • Язык локализации.
  • Конфигурация приложения (адреса сервисов, фичи-флаги).
  • Данные, к которым обращаются многие модули (например, кэш или метаданные).

Здесь глобальное состояние делает код проще:

  • Вам не нужно протаскивать одни и те же параметры через десятки уровней вызовов.
  • Модули могут быть независимыми, но разделять одни и те же данные.

Смотрите на пример с темой оформления:

// globalTheme.ts
// Здесь мы описываем простое глобальное состояние темы
let currentTheme: 'light' | 'dark' = 'light';

export function getTheme() {
  // Чтение значения темы
  return currentTheme;
}

export function setTheme(theme: 'light' | 'dark') {
  // Изменение глобального состояния
  currentTheme = theme;
}

Любой компонент может импортировать getTheme и setTheme и работать с одной темой. Это проще, чем передавать theme во все компоненты отдельно.

Кэширование и производительность

Часто глобальное состояние используют для кэшей и одноразовой инициализации:

  • Кэш результатов тяжелых запросов.
  • Пул соединений с базой данных.
  • Один экземпляр HTTP-клиента.

Пример на Go с пулом соединений:

package db

import "database/sql"

// Здесь мы объявляем глобальный объект соединения с БД
var Conn *sql.DB

// Init открывает соединение один раз
func Init(dsn string) error {
    // Здесь мы открываем соединение
    c, err := sql.Open("postgres", dsn)
    if err != nil {
        return err
    }
    // Сохраняем в глобальную переменную
    Conn = c
    return nil
}

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

Состояние между запросами и сеансами

В веб-приложениях и мобильных приложениях нужно хранить данные между «экранами» и «страницами»:

  • Авторизационный токен.
  • Корзина покупок.
  • Позиция в навигации.

Здесь глобальное состояние (например, Redux store или Vuex store) облегчает жизнь. Вы можете:

  • Подписать компонент на нужный кусок стейта.
  • Отправлять действия (actions), не думая, кто их обработает.
  • Сохранять часть состояния в localStorage или в IndexedDB.

Проблемы и риски глобального состояния

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

Скрытые зависимости

Код, который читает и пишет в глобальные переменные, формально не принимает никаких параметров, но при этом зависит от внешних данных.

// userService.ts
// Здесь мы неявно используем глобальный store
import { store } from './store';

export function getCurrentUserName() {
  // Зависимость есть, но она не видна в сигнатуре функции
  const state = store.getState();
  return state.user?.name ?? 'Guest';
}

По сигнатуре getCurrentUserName() кажется, что функция «чистая» — никаких аргументов. Но на самом деле она зависит от глобального состояния store.

Это усложняет:

  • Понимание кода (вам нужно знать, откуда берется store).
  • Повторное использование функций (без store они не работают).
  • Тестирование (вам нужно подменять глобальное состояние в тестах).

Состояние сложно отслеживать

Когда состояние можно менять откуда угодно, становится трудно понять:

  • Кто именно изменил значение.
  • В каком порядке произошли изменения.
  • Почему именно сейчас состояние стало неконсистентным.

На практике это выражается в таких симптомах:

  • «Иногда» приложение ведет себя странно.
  • Ошибку можно воспроизвести только при специфической последовательности действий.
  • В логах не видно очевидной причины изменения.

Проблемы с тестированием

Тесты часто должны выполняться независимо и в произвольном порядке. Если вы используете глобальное состояние, оно может «протекать» между тестами.

Пример хрупкого теста на JavaScript:

// globalState.js
// Здесь мы описываем глобальное состояние
export const state = {
  counter: 0,
};

// increment.js
// Здесь мы изменяем глобальное состояние
import { state } from './globalState';

export function increment() {
  state.counter += 1;
}

// test.js
import { state } from './globalState';
import { increment } from './increment';

test('increment increases counter', () => {
  increment();
  // Здесь мы ожидаем, что счетчик равен 1
  expect(state.counter).toBe(1);
});

test('increment again', () => {
  increment();
  // Если тесты запускать в другом порядке, состояние может отличаться
  expect(state.counter).toBe(1); // Может упасть, если предыдущий тест уже менял counter
});

Здесь тесты зависят от глобального счетчика. Чтобы это исправить, нужно каждый раз сбрасывать состояние или использовать инкапсулированный стор, который легко переинициализировать.

Многопоточность и гонки данных

В многопоточных и асинхронных средах глобальное состояние особенно опасно.

В Go:

var counter int

func Increment() {
    // Здесь мы обращаемся к общей переменной из разных горутин
    counter++
}

Если несколько горутин вызовут Increment() одновременно, вы можете получить состояние гонки (data race). Корректное значение счетчика будет неопределенным.

Чтобы это исправить, вам нужно синхронизировать доступ:

import "sync"

// Здесь мы защищаем глобальное состояние мьютексом
var (
    counter int
    mu      sync.Mutex
)

func Increment() {
    // Здесь мы блокируем мьютекс
    mu.Lock()
    // Теперь только одна горутина может изменить counter
    counter++
    // Освобождаем мьютекс
    mu.Unlock()
}

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

Усложнение рефакторинга

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

Глобальный объект, от которого зависит много кода, превращается в «монолит внутри монолита».

Когда глобальное состояние оправдано

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

Настройки и конфигурация приложения

Здесь глобальное состояние часто удобно и достаточно безопасно, если соблюдать несколько правил:

  • Конфигурация иммутабельна (после инициализации ее нельзя изменить).
  • Инициализация происходит один раз при старте приложения.
  • Конфигурация отделена от бизнес-логики.

Пример на Go:

package config

// Здесь мы объявляем глобальную конфигурацию, но не даем ее изменять после инициализации
type Config struct {
    DBURL string
}

// Иммутабельная ссылка на конфиг
var appConfig *Config

// InitConfig вызывается один раз при старте
func InitConfig(dbURL string) {
    // Здесь мы задаем конфигурацию только один раз
    appConfig = &Config{DBURL: dbURL}
}

// GetConfig возвращает ссылку только для чтения
func GetConfig() *Config {
    // Здесь мы просто отдаем ссылку, менять ее нельзя по контракту
    return appConfig
}

Здесь вы по сути используете глобальное состояние, но ограничиваете его изменение.

Централизованный стор UI-состояния

Во фронтенде глобальный стор (Redux, Vuex и т.п.) — стандартный способ хранить:

  • Состояние авторизации.
  • Глобальные фильтры.
  • Данные, разделяемые несколькими страницами.

Смотрите, как это может выглядеть в React с использованием Context API как простого глобального стейта.

// AppStateContext.tsx
import React, { createContext, useContext, useState } from 'react';

// Здесь мы описываем форму глобального состояния
type AppState = {
  user: { name: string } | null;
  setUser: (user: { name: string } | null) => void;
};

// Создаем контекст с дефолтным значением null
const AppStateContext = createContext<AppState | null>(null);

// Провайдер оборачивает приложение и создает глобальное состояние
export function AppStateProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<{ name: string } | null>(null);

  return (
    // Здесь мы предоставляем состояние через контекст
    <AppStateContext.Provider value={{ user, setUser }}>
      {children}
    </AppStateContext.Provider>
  );
}

// Хук для удобного доступа
export function useAppState() {
  const ctx = useContext(AppStateContext);
  if (!ctx) {
    throw new Error('useAppState must be used within AppStateProvider');
  }
  return ctx;
}

Любой компонент может вызвать useAppState() и работать с общим пользователем. При этом:

  • Источник глобального состояния явно виден.
  • Доступ ограничен только компонентами внутри AppStateProvider.
  • В тестах вы можете подменить провайдер.

Кэш и одноразовая инициализация

Кэширование дорогостоящих операций (например, запросов к API) через глобальное состояние — нормальная практика, особенно если:

  • Кэш прозрачен для пользователя.
  • Вы контролируете жизненный цикл кэша.

Пример простого кэша в Node.js:

// cache.ts
// Здесь мы описываем глобальный кэш запросов
const cache = new Map<string, unknown>();

export function getFromCache<T>(key: string): T | undefined {
  // Чтение значения из кэша
  return cache.get(key) as T | undefined;
}

export function setToCache<T>(key: string, value: T): void {
  // Запись значения в кэш
  cache.set(key, value);
}

Если вы понимаете, что кэш — это глобальное состояние, вы сразу начнете думать:

  • Когда его очищать.
  • Как избежать утечек памяти.
  • Не повлияет ли он на тесты.

Как уменьшить вред от глобального состояния

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

Делайте состояние максимально локальным

Основной принцип: поднимайте состояние только настолько высоко, насколько нужно, и не выше.

Во фронтенде это часто формулируется как «lift state up». Глобальным должно быть только то, что реально нужно многим частям приложения.

Например, не нужно помещать в глобальный Redux store:

  • Состояние одного модального окна, которое используется только в одном месте.
  • Локальные формы, которые живут только на одной странице.

Лучше оставить их внутри компонента.

Инкапсулируйте доступ к глобальному состоянию

Вместо того чтобы экспортировать глобальную переменную напрямую, создайте небольшой модуль-обертку с четким API.

Плохой вариант:

// state.ts
// Здесь мы экспортируем объект напрямую
export const state = {
  user: null as { name: string } | null,
};

Лучше так:

// userState.ts
// Здесь мы скрываем внутреннюю структуру состояния
let user: { name: string } | null = null;

export function getUser() {
  // Чтение пользователя
  return user;
}

export function setUser(newUser: { name: string } | null) {
  // Изменение пользователя
  user = newUser;
}

Так вы:

  • Можете поменять внутреннюю структуру, не переписывая весь код.
  • Можете добавить валидацию, логи, побочные эффекты при изменении.
  • Легче подмените реализацию для тестов.

Используйте Dependency Injection (внедрение зависимостей)

В серверных приложениях (Go, Java, .NET) часто применяют внедрение зависимостей вместо прямого использования глобальных переменных.

Смотрите, как можно уйти от глобального db.Conn в Go.

// db.go
package db

import "database/sql"

// Здесь мы определяем интерфейс для работы с БД
type DB interface {
    Query(query string, args ...any) (*sql.Rows, error)
    Exec(query string, args ...any) (sql.Result, error)
}
// user_repo.go
package user

import "myapp/db"

type Repository struct {
    db db.DB
}

// NewRepository получает зависимость явно
func NewRepository(database db.DB) *Repository {
    // Здесь мы сохраняем ссылку на интерфейс БД
    return &Repository{db: database}
}

func (r *Repository) GetUser(id int) (User, error) {
    // Здесь мы используем переданную зависимость
    rows, err := r.db.Query("SELECT id, name FROM users WHERE id = $1", id)
    // ...
    return User{}, err
}

Теперь:

  • Вы можете передать реальное соединение в main.
  • В тестах вы можете передать фейковую реализацию db.DB.

Глобальное состояние (db.Conn) может остаться только в main, а бизнес-логика будет от него независима.

Избегайте мутабельного глобального состояния

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

В Redux эта идея реализована так:

  • Состояние не мутируется напрямую.
  • Любое изменение — это новый объект.
  • Все изменения происходят через actions.

Смотрите упрощенный пример:

// store.ts
// Здесь мы описываем неизменяемое глобальное состояние
type State = {
  counter: number;
};

// Начальное состояние
let state: State = { counter: 0 };

// Здесь мы описываем тип действия
type Action =
  | { type: 'increment' }
  | { type: 'decrement' };

// Reducer описывает, как из старого состояния и действия получить новое
function reducer(current: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      // Возвращаем новый объект состояния
      return { ...current, counter: current.counter + 1 };
    case 'decrement':
      return { ...current, counter: current.counter - 1 };
    default:
      return current;
  }
}

// dispatch изменяет состояние только через reducer
export function dispatch(action: Action) {
  // Здесь мы пересчитываем состояние
  state = reducer(state, action);
}

// getState только возвращает текущее состояние
export function getState(): State {
  return state;
}

Здесь глобальное состояние все еще есть, но:

  • Легче отследить, какие действия привели к каким изменениям.
  • Можно логировать последовательность actions.
  • Проще откатить состояние назад (time travel).

Отделяйте глобальное состояние от бизнес-логики

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

То есть:

  • Ваши функции бизнес-логики принимают данные и возвращают результат.
  • Глобальное состояние используется только на «границах» приложения: контроллерах, обработчиках HTTP, компонентам UI.

Покажу, как это выглядит схематично:

// domain/calcPrice.ts
// Здесь мы описываем чистую бизнес-функцию
export function calcPrice(base: number, discount: number): number {
  // Функция зависит только от аргументов
  return base - base * discount;
}
// app/useCases.ts
import { calcPrice } from '../domain/calcPrice';
import { getUser } from '../state/userState';

// Здесь мы связываем глобальное состояние и бизнес-функцию
export function getUserPrice(basePrice: number) {
  // Читаем глобальное состояние
  const user = getUser();
  const discount = user ? 0.1 : 0;
  // Здесь мы просто вызываем чистую функцию
  return calcPrice(basePrice, discount);
}

В итоге:

  • Бизнес-правила (calcPrice) легко тестировать в отрыве от глобального состояния.
  • Слой приложения (getUserPrice) работает как мост между глобальным стейтом и доменной логикой.

Практические примеры управления глобальным состоянием

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

Пример 1: Мини-стор в чистом JavaScript

Смотрите, как можно сделать простой стор без фреймворков.

// store.js
// Здесь мы реализуем минималистичный глобальный стор

// Внутреннее состояние недоступно напрямую
let state = {
  user: null,
  theme: 'light',
};

// Список подписчиков
const listeners = [];

// Функция для чтения состояния
export function getState() {
  // Здесь мы возвращаем копию, чтобы избежать прямой мутации
  return { ...state };
}

// Функция для обновления состояния
export function setState(partial) {
  // Здесь мы объединяем старое состояние с новым фрагментом
  state = { ...state, ...partial };
  // Уведомляем всех подписчиков об изменении
  listeners.forEach((listener) => listener(state));
}

// Подписка на изменения
export function subscribe(listener) {
  // Здесь мы добавляем слушателя
  listeners.push(listener);
  // Возвращаем функцию отписки
  return () => {
    const index = listeners.indexOf(listener);
    if (index > -1) {
      listeners.splice(index, 1);
    }
  };
}

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

// componentA.js
import { getState, setState, subscribe } from './store.js';

// Чтение состояния один раз
const { theme } = getState();

// Подписка на изменения
const unsubscribe = subscribe((newState) => {
  // Здесь можно перерисовать компонент
  console.log('New theme', newState.theme);
});

// Изменение состояния
function onToggleTheme() {
  // Здесь мы обновляем только поле theme
  setState({ theme: 'dark' });
}

Здесь вы видите:

  • Состояние глобальное, но модульно инкапсулировано.
  • Прямой записи в объект состояния нет — все только через setState.
  • Компоненты могут подписываться и отписываться.

Пример 2: Глобальное состояние запроса в React

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

Здесь я покажу упрощенный вариант с Context.

// ApiStateContext.tsx
import React, { createContext, useContext, useState } from 'react';

type User = { id: number; name: string };

type ApiState = {
  users: User[];                 // Кэш списка пользователей
  isLoading: boolean;            // Флаг загрузки
  error: string | null;          // Ошибка
  loadUsers: () => Promise<void>;
};

const ApiStateContext = createContext<ApiState | null>(null);

export function ApiStateProvider({ children }: { children: React.ReactNode }) {
  // Здесь мы создаем локальное состояние в провайдере,
  // но по факту оно станет глобальным для всех дочерних компонентов
  const [users, setUsers] = useState<User[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function loadUsers() {
    // Здесь мы сохраняем флаг, что началась загрузка
    setIsLoading(true);
    setError(null);
    try {
      // Здесь вы бы сделали реальный запрос к API
      const response = await fetch('/api/users');
      const data = (await response.json()) as User[];

      // Обновляем глобальное состояние списка пользователей
      setUsers(data);
    } catch (e) {
      // Сохраняем текст ошибки
      setError((e as Error).message);
    } finally {
      // Снимаем флаг загрузки
      setIsLoading(false);
    }
  }

  return (
    <ApiStateContext.Provider
      value={{ users, isLoading, error, loadUsers }}
    >
      {children}
    </ApiStateContext.Provider>
  );
}

// Хук для доступа к глобальному состоянию API
export function useApiState() {
  const ctx = useContext(ApiStateContext);
  if (!ctx) {
    throw new Error('useApiState must be used within ApiStateProvider');
  }
  return ctx;
}

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

// UsersList.tsx
import React, { useEffect } from 'react';
import { useApiState } from './ApiStateContext';

export function UsersList() {
  // Здесь мы получаем доступ к глобальному состоянию API
  const { users, isLoading, error, loadUsers } = useApiState();

  useEffect(() => {
    // При первом рендере загружаем пользователей
    loadUsers();
  }, [loadUsers]);

  if (isLoading) {
    return <div>Загрузка...</div>;
  }

  if (error) {
    return <div>Ошибка {error}</div>;
  }

  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

Здесь:

  • Состояние users глобально для всех компонент внутри ApiStateProvider.
  • Вы не дублируете запросы при переходах между страницами.
  • В тестах вы можете подменить провайдер и передать фиктивные данные.

Пример 3: Безопасное глобальное состояние в многопоточном Go

Теперь давайте посмотрим, как аккуратно сделать глобальный кэш в Go.

package cache

import "sync"

// Здесь мы описываем потокобезопасный глобальный кэш

// структура, инкапсулирующая мьютекс и карту
type Cache struct {
    mu    sync.RWMutex
    store map[string]interface{}
}

// Глобальный экземпляр кэша
var globalCache = &Cache{
    store: make(map[string]interface{}),
}

// Set сохраняет значение в кэш
func Set(key string, value interface{}) {
    // Здесь мы блокируем мьютекс на запись
    globalCache.mu.Lock()
    defer globalCache.mu.Unlock()

    // Обновляем карту
    globalCache.store[key] = value
}

// Get получает значение из кэша
func Get(key string) (interface{}, bool) {
    // Здесь мы блокируем мьютекс на чтение
    globalCache.mu.RLock()
    defer globalCache.mu.RUnlock()

    // Читаем из карты
    val, ok := globalCache.store[key]
    return val, ok
}

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

package main

import (
    "fmt"
    "myapp/cache"
)

func main() {
    // Здесь мы записываем значение в глобальный кэш
    cache.Set("x", 42)

    // А здесь читаем
    if v, ok := cache.Get("x"); ok {
        fmt.Println(v) // 42
    }
}

Здесь:

  • Глобальное состояние есть, но оно скрыто в пакете cache.
  • Доступ синхронизирован, нет data race.
  • В тестах можно заменить globalCache на другой экземпляр (через дополнительные настройки).

Заключение

Глобальное состояние — мощный, но опасный инструмент. Оно:

  • Упрощает доступ к общим данным.
  • Уменьшает количество явной передачи параметров.
  • Позволяет реализовать кэш, общие настройки, централизованный стор.

Одновременно с этим global state:

  • Увеличивает связанность модулей.
  • Создает скрытые зависимости.
  • Усложняет тестирование и рефакторинг.
  • В многопоточной среде требует аккуратной синхронизации.

Ключевая идея, которую стоит вынести:

  • Не нужно полностью запрещать себе глобальное состояние.
  • Важно осознанно его применять и ограничивать зону влияния.

Практические рекомендации:

  • Делайте состояние локальным по умолчанию, глобальным — только по необходимости.
  • Инкапсулируйте глобальное состояние в модулях и давайте к нему доступ через функции.
  • Используйте иммутабельность и явные события (actions), где это возможно.
  • Используйте dependency injection, чтобы бизнес-логика не зависела от глобальных переменных.
  • Отделяйте доменную логику от слоев, где живет глобальное состояние (UI, инфраструктура).

Так вы сохраните удобство global state и при этом снизите архитектурный «долг», который он обычно приносит.

Частозадаваемые технические вопросы по теме и ответы

Как протестировать код, зависящий от глобального состояния

Лучший путь — добавить слой абстракции. Вместо прямого импорта глобальной переменной экспортируйте функции доступа. В тестах подменяйте эти функции на фейки или моки.

Пример:

  • В userState.ts экспортируете getUser.
  • В тестах используете jest.mock или аналог, чтобы вернуть нужного пользователя.

Как временно изменить глобальное состояние в тесте и потом вернуть обратно

Сделайте вспомогательную функцию-хелпер:

  • Сохраняете старое значение глобальной переменной.
  • Устанавливаете новое перед тестом.
  • В afterEach или defer (в Go) возвращаете старое значение.

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

Как перенести существующую логику с глобальными переменными на dependency injection

Двигайтесь по шагам:

  1. Выделите интерфейсы для зависимостей (например, DB, Logger).
  2. Переделайте функции и методы так, чтобы они принимали эти интерфейсы как аргументы или поля структур.
  3. На уровне main или корневого модуля создайте реальные реализации и передайте их вниз.
    Глобальные переменные можно оставить только в main, пока идет миграция.

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

Используйте:

  • Мьютексы (sync.Mutex, sync.RWMutex в Go).
  • Специализированные типы (atomic-переменные, потокобезопасные коллекции).
    Вокруг глобального состояния создайте API, который всегда берет и отпускает блокировку внутри, чтобы вызывающий код не думал о синхронизации.

Как решить проблему «prop drilling» без введения большого количества глобального состояния во фронтенде

Для данных, которые нужны многим компонентам, используйте:

  • Context API (в React).
  • Provide/Inject (в Vue).
  • Локальные стора на уровне фич-модулей.

Не обязательно поднимать все в один глобальный Redux или Vuex. Делите состояние на «области» — по фичам или страницам — и делайте для них отдельные небольшие контексты или сторы.

Стрелочка влевоЛокальное состояние local state в веб разработке

Постройте личный план изучения Vue до уровня Middle — бесплатно!

Vue — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Vuex - полное руководство по управлению состоянием во Vue приложенияхРеактивные ссылки ref - полный разбор для разработчиковРеактивные объекты reactive-objects - подробное руководство с примерамиРеактивные переменные - концепция reactive и практические примерыМеханизм Provide Inject - как он работает и когда применятьPinia современный менеджер состояния для VueЛокальное состояние local state в веб разработкеГлобальное состояние в приложениях - global state
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateИспользование query-параметров и их обработка в маршрутах VueРазрешение конфликтов и ошибок с помощью Vue resolveЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
Функция append в Go GolangОтображение компонента mounted - практическое руководствоХуки жизненного цикла компонентов - полное руководство для разработчиковУничтожение компонента destroyed - как правильно очищать ресурсы и подпискиИнициализация данных в состоянии created - как и когда подготавливать данные в приложенииОбновление компонента beforeUpdate во VueМонтирование компонента - хук beforeMount в VueРазрушение компонента во Vue - beforeDestroy и beforeUnmountСоздание экземпляра beforeCreate - полный разбор жизненного цикла
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Слоты компонента - концепция и практическое использованиеРегистрация компонентов component-registration в приложениях с внедрением зависимостейProps компонента в React - полный разбор с примерамиФункциональные компоненты в React - функциональный подход к построению интерфейсовСобытия компонента - events в современных интерфейсахДинамические компоненты - dynamic-componentsСоздание компонента component - практическое руководствоАсинхронные компоненты async-components - практическое руководство
Наблюдатели watchers - от паттерна до практических реализацийУправление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхТипизация и использование TypeScript в VuejsСоздание и использование UI Kit для Vue приложенийШаблоны Vue templates - практическое руководство для разработчиковИспользование шаблонов в Vue js для построения интерфейсовИспользование Swiper для создания слайдеров в VueРабота со стилями и стилизацией в VueСтруктура и особенности Single File Components SFC в VueРабота со SCSS в проектах на Vue для стилизацииРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueРеактивность Vue reactivity - как это работает под капотом и как этим пользоватьсяПонимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueОпции компонента в Go - паттерн component-optionsРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование методов в компонентах Vue для обработки логикиИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаРабота с ключами key в списках и компонентах VueОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование хуков жизненного цикла в VueОрганизация сеток и гридов для верстки интерфейсов на VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreВычисляемые свойства computed во Vue.jsСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueОсновы Vue - vue-basics для уверенного стартаИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueТелепортация - архитектура и реализация в серверных приложенияхРабота с teleport для управления DOM во VueSuspense в React - управление асинхронными данными и ленивой загрузкойПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиПлагины Vue vue-plugins - полное практическое руководствоРуководство по nextTick для работы с DOMМиксины - mixins в современном программированииJSX в Vue с использованием плагина vue-jsxСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьКастомные элементы - Custom Elements в современном JavaScriptИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Vue 3 и Pinia

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий