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

Основы контекста

Контекст (Context) в Inferno предназначен для передачи данных через дерево компонентов без необходимости явной прокладки этих данных через каждый уровень промежуточных компонентов. Он особенно полезен для глобальных настроек, тем, языковых локализаций и других данных, которые должны быть доступны в глубоко вложенных компонентах.

Контекст создаётся с помощью функции createContext из пакета inferno:

import { createContext } from 'inferno';

const ThemeContext = createContext('light'); // 'light' — значение по умолчанию

Здесь 'light' — это значение по умолчанию, которое используется, если компонент-потребитель контекста не находится внутри провайдера.

Провайдер контекста

Провайдер (Provider) отвечает за предоставление значения контекста всему дереву компонентов, находящемуся ниже него:

import { render } from 'inferno';
import { ThemeContext } from './ThemeContext';

const App = () => (
  <ThemeContext.Provider value="dark">
    <Toolbar />
  </ThemeContext.Provider>
);

В примере выше все компоненты внутри Toolbar смогут получить доступ к значению "dark". Значение провайдера может быть динамическим, что позволяет изменять состояние контекста и реактивно обновлять компоненты.

import { useState } from 'inferno';

const App = () => {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar />
      <button onCl ick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Переключить тему
      </button>
    </ThemeContext.Provider>
  );
};

Потребители контекста

Компоненты-потребители (Consumer) получают текущее значение контекста через функцию-рендер. Это позволяет использовать значение напрямую внутри JSX:

import { ThemeContext } from './ThemeContext';

const Toolbar = () => (
  <ThemeContext.Consumer>
    {theme => <div className={`toolbar ${theme}`}>Панель инструментов</div>}
  </ThemeContext.Consumer>
);

Потребитель автоматически реагирует на изменения значения контекста и перерендеривается при его обновлении. Важно понимать, что только компоненты, находящиеся внутри соответствующего Provider, получают актуальное значение. Если компонент вне провайдера, используется значение по умолчанию.

useContext — современный способ работы с контекстом

В функциональных компонентах удобнее использовать хук useContext:

import { useContext } from 'inferno';
import { ThemeContext } from './ThemeContext';

const Toolbar = () => {
  const theme = useContext(ThemeContext);
  return <div className={`toolbar ${theme}`}>Панель инструментов</div>;
};

Хук делает код более компактным и читаемым, избавляя от необходимости оборачивать JSX в <ThemeContext.Consumer>.

Передача данных через дерево компонентов

Помимо контекста, данные традиционно передаются через пропсы (props). Это базовый механизм, который обеспечивает односторонний поток данных сверху вниз. Комбинирование пропсов и контекста позволяет гибко управлять состоянием:

const Button = ({ label, onClick }) => (
  <button onCl ick={onClick}>{label}</button>
);

const Toolbar = () => (
  <div>
    <Button label="Сохранить" onCl ick={() => console.log('Сохранено')} />
  </div>
);

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

Динамическое обновление и оптимизация

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

  • Разделять контексты по функциональным зонам (например, отдельный контекст для темы, отдельный для аутентификации).
  • Использовать мемоизацию значений, передаваемых через value:
import { useMemo, useState } from 'inferno';

const App = () => {
  const [theme, setTheme] = useState('light');

  const contextValue = useMemo(() => ({ theme, toggle: () => setTheme(theme === 'light' ? 'dark' : 'light') }), [theme]);

  return (
    <ThemeContext.Provider value={contextValue}>
      <Toolbar />
    </ThemeContext.Provider>
  );
};

useMemo предотвращает ненужные пересоздания объекта при каждом рендере, что снижает количество перерендеров дочерних компонентов.

Контекст и классовые компоненты

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

class Toolbar extends Component {
  render() {
    return <div className={`toolbar ${this.context}`}>Панель инструментов</div>;
  }
}

Toolbar.contextType = ThemeContext;

Присвоение contextType позволяет использовать this.context внутри классового компонента для доступа к текущему значению контекста.

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

  • Контекст не заменяет управление состоянием на уровне компонентов; он служит для глобальных или кросс-компонентных данных.
  • Не рекомендуется хранить в контексте часто изменяемые данные, которые приводят к множественным перерендерингам большого числа компонентов.
  • Для сложных приложений имеет смысл комбинировать контексты с локальными состояниями, чтобы минимизировать избыточные рендеры.

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