Как стать автором
Обновить

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

Время на прочтение4 мин
Количество просмотров7.4K
Привет, это мой первый пост на Хабр. Надеюсь, Вам будет интересно.

Итак, я хочу начать серию постов, напрямую или косвенно касающихся создания ui-kit.

image

Задача данного поста: Найти решение контроля темы приложения, компоненты которого выполнены в React.js. Будем использовать две глобальные темы — dark и light.

В данном примере я буду использовать модуль create-react-context, для создания контекста.

Начнем с создания папки в корне проекта (src/) под названием theme-context. Структура данной папки будет выглядеть следующим образом:

theme-context/
   ThemeConsumer/
      ThemeConsumer.js
      index.js
    ThemeProvider/
      ThemeProvider.js
      index.js
  constants.js 
  context.js
  index.js

Лично, я всегда начинаю с файла index.js. Делаешь все импорты и экспорты вначале, и потом уже голова о них не болит.

theme-context/index.js

export { ThemeProvider } from './ThemeProvider';
export { ThemeConsumer } from './ThemeConsumer';

theme-context/ThemeConsumer/index.js

export { ThemeConsumer } from './ThemeConsumer';

theme-context/ThemeProvider/index.js

export { ThemeProvider } from './ThemeProvider';

theme-context/context.js

Далее, мы создадим контекст при помощи createContext (простите за каламбур), используя модуль отсюда.

import createContext from 'create-react-context';

const { Provider, Consumer } = createContext();

export { Provider, Consumer };

Импортируем createContext, деструктурируем его на Provider и Consumer, и экспортируем их.

theme-context/constants.js

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

export const themeLight = 'light';
export const themeDark = 'dark';
export const defaultTheme = themeLight;
export const themes = [themeLight, themeDark];

Как я и говорил ранее, у нашего приложения будет две темы — лайт и дарк.

theme-context/ThemeProvider/ThemeProvider.js

Здесь речь пойдет о провайдере — компоненте, который доступен в каждом объекте React.Context. Он позволяет консюмерам слушать и реагировать на изменения контекста.

В нашем примере, проп Провайдера — это theme, который будет передан всем Консюмерам-потомкам данного Providerа.

import React from 'react';
import { Provider } from '../context';
import { defaultTheme, themes } from '../constants';

function ThemeProvider({ theme, children }) {
  return <Provider value={theme}>{children}</Provider>;
}

export { ThemeProvider };

theme-context/ThemeConsumer/ThemeConsumer.js

В данном файле мы будем работать с Consumer — это компонент, который «слушает, ждет» изменения контекста. Дитя (Children) данного компонента — функция. Это обязательное требование при использовании Consumer.

Данная функция получает значения текущего контекста и возвращает React Node, проще говоря — компонент.

Из документации: значение аргумента (в нашем случае {theme => /* визуализировать что-либо на основе значения контекста */}) будет равно пропсу theme ближайшего, вышестоящего в дереве Provider для данного контекста.

import React from 'react';
import { defaultTo } from 'lodash';
import { Consumer } from '../context';
import { defaultTheme, themes } from '../constants';

function ThemeConsumer(props) {
  return <Consumer>{theme => props.children(defaultTo(theme, props.defaultTheme))}</Consumer>;
}

export { ThemeConsumer };

Тут стоит обратить внимание на следующее:
Если тема не была выбрана эксплицитно, нам нужно, чтобы тема компонентов была выбрана автоматически, для этого я использую утиль из lodashdefaultTo. Однако добиться данной функциональности можно множеством иных способов.

Вот и все, контекст темы готов к использованию!

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

.my-class {
  font-family: sans-serif;
  text-align: center;
  font-size: 30px;
}

.my-class-light {
  color: #39cccc;
}

.my-class-dark {
  color: #001a33;
}

import React from "react";
import ReactDOM from "react-dom";
import cx from "classnames";
import { ThemeConsumer, ThemeProvider } from "./theme-context";

import "./styles.css";

function MyComponent() {
  const renderMyComponent = theme => {
    const myComponentClassName = cx("my-class", {
      "my-class-dark": theme === "dark",
      "my-class-light": theme === "light"
    });
    return (
      <div className={myComponentClassName}>
        <h1>Текст в цвете текущей темы</h1>
      </div>
    );
  };
  return <ThemeConsumer>{theme => renderMyComponent(theme)}</ThemeConsumer>;
};

function App() {
  return (
      <MyComponent />
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
// поменять текушую тему можно сменив theme на dark
<ThemeProvider theme="light">
  <App />
</ThemeProvider>
, rootElement);

Итак, мы обернули наш <App /> в провайдере, и теперь тема стала доступна всем компонентам-консюмерам в нашем приложении. Далее, <App /> возвращает <MyComponent />, это Консюмер, и он создаст наш компонент и передаст ему тему нашего приложения. А уже имея тему в виде аргумента:

<ThemeConsumer>{theme => renderMyComponent(theme)}</ThemeConsumer>

мы сможем использовать ее при создании компонента
  const renderMyComponent = theme => {
    // создание компонента основываясь на теме
};

Рабочий код можно посмотреть тут.

Вот и все, надеюсь, Вы найдете данный пост полезным. В следующем посту я попробую создать media-context, функциональность которого поможет нам визуализировать компоненты на оснавании девайса юзера.

Спасибо.
Теги:
Хабы:
Всего голосов 11: ↑11 и ↓0+11
Комментарии8

Публикации

Истории

Работа

Ближайшие события