Привет, данная статья является продолжением вот этого поста, где я решил начать серию небольших учебных пособий по созданию UI-Kit. Там описан метод использования контекста для создания темы приложения. Сегодня я хочу дополнить приложение еще одним контекстом, который поможет нам показывать компоненты UI, основываясь на девайсе юзера.
В данном примере я также буду использовать модуль
Дерево задания будет идентичным с предыдущим. Назовем новую папку media-context.
Быстренько разберемся с index'ами.
media-context/index.js
theme-context/MediaConsumer/index.js
theme-context/MediaProvider/index.js
theme-context/context.js
Как и ранее, создаем
Импортируем, деструктурируем и экспортируем полученные компоненты.
media-context/constants.js
Переменные сохраним тут.
В адаптивном подходе юзер в основном использует 4 вида девайсов — настольный, планшет, мобильный и нативное приложение (native). Другие возможные устройства: смарт-тв, часы и т.п. В данном примере будем использовать только mobile & desktop.
theme-context/MediaProvider/MediaProvider.js
Как мы уже знаем, в
Props
theme-context/MediaConsumer/MediaConsumer.js
Описание функциональности консюмера можно найти в предыдущей статье, но если вкратце, то
В данном случае мы передаем два параметра в
Теперь в нашем приложении доступен новый контекст, давайте им воспользуемся.
Для использования медиаконтеста нам потребуется медиадетектор (для получения информации о девайсе юзера). В нашем приложении медиадетектора нет, поэтому возьмем его отсюда — react-device-detect, а именно флаг isMobile.
Как и с контекстной темой, мы обернем наш тестовый компонент в медиапровайдере. Значение пропсов, которые провайдер передаст своему консюмеру, будут зависть от устройства клиента. Зашел с мобильного, посылаем мобильную версию сайта, с компьютера — десктопную. Рабочий код можно посмотреть тут.
Чтобы увидеть разницу, зайдите по ссылке с настольного и мобильного браузера.
Все! Теперь наше приложение имеет два контекста, которые помогут нам создавать более разносторонние и гибкие компоненты в нашем UI-Kit. В следующем посту я попробую создать комплексный компонент с использованием обоих контекстов. Надеюсь, Вы найдете статью полезной.
Спасибо.
В данном примере я также буду использовать модуль
create-react-context
.Дерево задания будет идентичным с предыдущим. Назовем новую папку media-context.
media-context/
MediaConsumer/
MediaConsumer.js
index.js
MediaProvider/
MediaProvider.js
index.js
constants.js
context.js
index.js
Быстренько разберемся с index'ами.
media-context/index.js
export { MediaProvider } from './MediaProvider';
export { MediaConsumer } from './MediaConsumer';
theme-context/MediaConsumer/index.js
export { MediaConsumer } from './MediaConsumer';
theme-context/MediaProvider/index.js
export { MediaProvider } from './MediaProvider';
theme-context/context.js
Как и ранее, создаем
createContext
(модуль нпм).import createContext from 'create-react-context';
const { Provider, Consumer } = createContext();
export { Provider, Consumer };
Импортируем, деструктурируем и экспортируем полученные компоненты.
media-context/constants.js
Переменные сохраним тут.
export const devices = ["mobile", "tablet", "desktop", "native"];
export const defaultDevice = "mobile";
В адаптивном подходе юзер в основном использует 4 вида девайсов — настольный, планшет, мобильный и нативное приложение (native). Другие возможные устройства: смарт-тв, часы и т.п. В данном примере будем использовать только mobile & desktop.
theme-context/MediaProvider/MediaProvider.js
Как мы уже знаем, в
React.Context
доступен Provider, который «кормит» консюмеров контекстом. Он позволяет им слушать и реагировать на изменения контекста.Props
Провайдера
— device
и touchable
. Напомню, что все значения переданные в value
провайдера станут доступными для консюмеров. import React from "react";
import PropTypes from "prop-types";
import { Provider } from "../context";
import { devices, defaultDevice } from "../constanst";
function MediaProvider(props) {
const device = devices.includes(props.device)
? props.device
: props.defaultDevice;
return <Provider value={{ ...props, device }}>{props.children}</Provider>;
}
MediaProvider.propTypes = {
device: PropTypes.oneOf(devices),
touchable: false,
children: PropTypes.node
};
MediaProvider.defaultProps = {
device: defaultDevice,
touchable: false
};
export { MediaProvider };
theme-context/MediaConsumer/MediaConsumer.js
Описание функциональности консюмера можно найти в предыдущей статье, но если вкратце, то
Consumer
реагирует на контекст и возвращает новый компонент.В данном случае мы передаем два параметра в
children
: вид девайса и является ли устройство сенсорным.import React from "react";
import PropTypes from "prop-types";
import { defaultTo } from "lodash";
import { devices, defaultDevice } from "../constanst";
import { Consumer } from '../context';
function MediaConsumer(props) {
return (
<Consumer>
{media => props.children({
touchable: defaultTo(media.touchable, props.defaultTouchable),
device: defaultTo(media.device, props.defaultDevice),
})}
</Consumer>
)
}
MediaConsumer.propTypes = {
defaultTouchable: PropTypes.bool,
defaultDevice: PropTypes.oneOf(devices),
children: PropTypes.func.isRequired
};
MediaConsumer.defaultProps = {
defaultDevice,
defaultTouchable: false
};
export { MediaConsumer };
Теперь в нашем приложении доступен новый контекст, давайте им воспользуемся.
import React from "react";
import ReactDOM from "react-dom";
import { isMobile } from "react-device-detect";
import cx from "classnames";
import { ThemeConsumer, ThemeProvider } from "./theme-context";
import { MediaConsumer, MediaProvider } from "./media-context";
import "./styles.css";
function renderSVG({ device, touchable }) {
if (device === "mobile" && touchable) {
return "https://image.flaticon.com/icons/svg/124/124114.svg";
}
if (device === "tablet" && touchable) {
return "https://image.flaticon.com/icons/svg/124/124099.svg";
}
if (device === "desktop") {
return "https://image.flaticon.com/icons/svg/124/124092.svg";
}
}
function MyComponent() {
const renderMyComponent = (theme, media) => {
const myComponentClassName = cx("my-class", {
"my-class-dark": theme === "dark",
"my-class-light": theme === "light"
});
return (
<div className="wrapperDiv">
<object
className={myComponentClassName}
data={renderSVG(media)}
type="image/svg+xml"
/>
</div>
);
};
return (
<MediaConsumer>
{media => (
<ThemeConsumer>
{theme => renderMyComponent(theme, media)}
</ThemeConsumer>
)}
</MediaConsumer>
);
}
function App() {
return (
<MediaProvider
device={isMobile ? "mobile" : "desktop"}
touchable={isMobile}
>
<ThemeProvider theme="light">
<MyComponent />
</ThemeProvider>
</MediaProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Для использования медиаконтеста нам потребуется медиадетектор (для получения информации о девайсе юзера). В нашем приложении медиадетектора нет, поэтому возьмем его отсюда — react-device-detect, а именно флаг isMobile.
Как и с контекстной темой, мы обернем наш тестовый компонент в медиапровайдере. Значение пропсов, которые провайдер передаст своему консюмеру, будут зависть от устройства клиента. Зашел с мобильного, посылаем мобильную версию сайта, с компьютера — десктопную. Рабочий код можно посмотреть тут.
Чтобы увидеть разницу, зайдите по ссылке с настольного и мобильного браузера.
Все! Теперь наше приложение имеет два контекста, которые помогут нам создавать более разносторонние и гибкие компоненты в нашем UI-Kit. В следующем посту я попробую создать комплексный компонент с использованием обоих контекстов. Надеюсь, Вы найдете статью полезной.
Спасибо.
andres_kovalev
Хабр все больше превращается в заметки о том как кто-то что-то сделал в стиле "чтобы не забыть". Почему бы Вам в своей заметке не указать, хотя бы какую-нибудь вводную, например, чем этот подход лучше media queries? Какие есть альтернативы и т.п.?
Koneru
Media queries не работает с css-in-js.
polarlord
В какой именно библиотеке они не работают? В styled components и emotion — работают превосходно.
Koneru
Да, вы правы, ошибся, тогда тоже интересует вопрос выше.
tsepen
Работает так же как и в css-in-css
camelCaseVlad Автор
Спасибо за комментарий. Сожалею, что статья показалась Вам неинтересной.
Не возьмусь утверждать, какой метод лучше или хуже. Но вот Вам пример из личного опыта, где я и команда решили использовать адаптивный метод. Архитектор приложения в своем решении указал, что юзер должен получать бандл только с тем кодом, который необходим ему для сессии — js, css и html, т.е. не давать клиенту код для десктопа, если он зашел с телефона.
aleki
Не вижу никакой корреляции между вашим примером и статьёй.
Carduelis
Ну, по правде говоря, это может быть лучше тем, что мы можем отслеживать изменения не размера экрана, а размера самого компонента. Вот тут открываются уже поистинне широкие возможности по кастомизации внешнего вида компонента не в зависимости от экрана монитора, а в зависимости от места, которое может быть доступно для этого компонента.
Carduelis
Но вешая observer на каждый триггер reflow не такая производительная вещь, особенно, если речь о кроссбраузерности.