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

от автора

Привет, это мой первый пост на Хабр. Надеюсь, Вам будет интересно.

Итак, я хочу начать серию постов, напрямую или косвенно касающихся создания 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, функциональность которого поможет нам визуализировать компоненты на оснавании девайса юзера.

Спасибо.


ссылка на оригинал статьи https://habr.com/ru/post/462963/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *