В какой-то момент я хотел решить несколько проблем с UI частью в приложении на React Native:
UI был слишком медленный. И здесь важно уточнить, что он был сделан с использованием styled-components.
В приложении не было возможности темизации, потому что токены импортировались как модуль в каждом компоненте. И как следствие, в приложении невозможно было сделать темную тему, потому что при смене токенов оно бы просто не перерендерилось.
Спойлер. Restyle решает обе проблемы, а еще дает дополнительные классные плюшки, о которых мы обязательно поговорим дальше в статье.
Restyle – что это?
The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.
Restyle предоставляет набор инструментов, которые позволят строить UI продуктов на основе токенов. Особенно очень хорошо такой подход залетает, когда в продукте есть Дизайн Система и этот набор токенов действительно определен.
Одно из основных преимуществ библиотеки Restyle по сравнению с той же styled-system, в том, что она не использует styled-components под капотом. В основе Restyle используются абстрактные стили StyleSheet, поэтому ожидается более высокая производительность.
Немного про токены
Что мы понимаем под токенами? Это цвета, отступы, радиусы и так далее – все строгие ограничения, касающиеся стилей компонентов.
У нас будет несколько групп токенов:
Цвета
{
"error500": "rgba(224, 25, 53, 1)",
"error300": "rgba(231, 78, 99, 1)",
"error100": "rgba(255, 242, 244, 1)",
"warning500": "rgba(255, 184, 0, 1)",
"warning100": "rgba(255, 246, 221, 1)",
"success500": "rgba(46, 181, 24, 1)",
"success100": "rgba(234, 248, 232, 1)",
"g100": "rgba(255, 255, 255, 1)",
"g200": "rgba(244, 245, 248, 1)",
"g300": "rgba(208, 211, 221, 1)",
"g400": "rgba(164, 166, 178, 1)",
"g500": "rgba(24, 24, 24, 1)",
}
Отступы
{
"1": 4,
"2": 8,
"3": 12,
"4": 16,
"5": 20,
"6": 24,
"7": 28,
"8": 32,
"9": 36,
"10": 40
}
Радиусы
{
"xxs": 4,
"xs": 8,
"s": 12,
"m": 18,
"l": 20,
"xl": 24,
"xxl": 32
}
Типографика
{
"headingXL": {
"fontSize": 40,
"lineHeight": 44,
"letterSpacing": -0.4
},
"headingL": {
"fontSize": 36,
"lineHeight": 40,
"letterSpacing": -0.36
},
"headingM": {
"fontSize": 28,
"lineHeight": 32,
"letterSpacing": -0.28
},
"headingS": {
"fontSize": 22,
"lineHeight": 26,
"letterSpacing": 0
},
"headingXS": {
"fontSize": 17,
"lineHeight": 22,
"letterSpacing": 0
},
"bodyL": {
"fontSize": 17,
"lineHeight": 24,
"letterSpacing": -0.34
},
"bodyM": {
"fontSize": 15,
"lineHeight": 22,
"letterSpacing": -0.3
},
"bodyS": {
"fontSize": 13,
"lineHeight": 16,
"letterSpacing": -0.26
}
}
Брейкпойнты
{
"s": 0,
"m": 360
}
А дальше эти токены нужно расшарить на всё приложение, для этого библиотека Restyle предоставляет ThemeProvider
:
import {ThemeProvider, createTheme} from '@shopify/restyle';
import {
colors,
spacing,
breakpoints,
textVariants,
borderRadii
} from './tokens';
import {Box,Text} from './';
const theme = createTheme({
colors,
spacing,
breakpoints,
textVariants,
borderRadii,
});
export const App = () => {
<ThemeProvider theme={theme}>
<Box
flex={1}
background="g200"
margin={2}
>
<Text variant="bodyM" color="g500">Привет!</Text>
</Box>
</ThemeProvider>
}
Box & Text
Компоненты Box
и Text
являются основными строительными блоками для UI. Box
создается на основе View
и нужен для оберток, Text
нужен для текстовых элементов соответственно. Они создаются один раз, и дальше их просто можно переиспользовать.
import {createBox, createText} from '@shopify/restyle';
export const Box = createBox();
export const Text = createText();
export const myComponent = () => {
return (
<Box
flex={1}
background="g200"
margin={2}
>
<Text variant="bodyM" color="g500">Привет!</Text>
</Box>
)
};
Text
имеет свой собственный проп variant
, где он как раз принимает значение токена типографики textVariant
.
Стили прям в разметке
Нет необходимости выносить стили отдельно или создавать отельные файлы. Стили можно добавлять прям в компоненте, используя Restyle компоненты Box
и Text
.
Да, все правильно, мы будем писать стили прям в JSX. А почему это хорошо?
Не нужно создавать бесконечные названия блоков (Container, Wrapper и т.д)
Мы сразу видим всю картину целиком, за что отвечает каждый блок
Стилей не так уж и много обычно
Мы все равно часто добавляем стили в JSX, будет все однообразно
Styled обертки сильно ухудшают производительность
Но все эти плюсы будут работать, если вы будете декомпозировать сложные компоненты на небольшие модули. В полотне верстки это будет выглядеть плохо.
Цвета
Цвета можно пробрасывать во все цветовые пропсы, используя ключи из темы (например g200
, g300
, g400
).
Цветовые пропсы
backgroundColor [bg]
color
shadowColor
textShadowColor
borderColor
В квадратных скобках сокращенный вариант пропса
Посмотрим на пример, как это может использоваться:
<Box
backgroundColor="g300"
borderColor="g400"
>
<Text
variant="bodyS"
color="g200"
>
Any text
</Text>
</Box>
Отступы
Все отступы тоже можно пробрасывать прям в компоненты, используя специальные пропсы для этого. В нашем случае они добавляют отступы, кратные 4px
.
Пропсы для отступов
margin [m], marginTop [mt], marginRight [mr], marginBottom [mb], marginLeft [ml], marginStart [ms], marginEnd[me], marginHorizontal [mx], marginVertical [my], padding [p], paddingTop [pt], paddingRight [pr], paddingBottom [pb], paddingLeft [pl], paddingStart [ps], paddingEnd [pe], paddingHorizontal [px], paddingVertical [py], gap [g], rowGap [rG], columnGap [cG]
В квадратных скобках сокращенный вариант пропса
// будет добавлен marginTop=12px и padding=8px
<Box
marginTop={3}
padding={2}
/>
BorderRadius
Радиус тоже берется из темы и задается следующим образом:
<Box
borderColor="g400"
borderStyle="solid"
borderWidth={1}
borderRadius="xs"
/>
Кастомные стили
Через свойство style
можно пробросить и обычные стили. Таким образом можно добавить разные исключения, например, если нужен отступ некратный 4 или цвет не из ДС.
// добавится отступ 5px и кастомный цвет
<Box style={{ padding: 5, backgroundColor: '#ccc' }} />
Размеры девайсов
В моей ДС определены два размера девайсов, что позволяет указывать разные стили следующим образом:
// s: от 0px до 360px
// m: больше 360px
// для s девайсов будет 4px отступ, а для m девайсов 8px
<Box marginTop={{ s: 1, m: 2 }} />
При помощи такой конструкции можно пробрасывать любые свойства, не только отступы.
Если написать просто marginTop={1}
, то стили применятся для всех устройств (s
и m
).
Если нам нужно написать какую-то логику в компоненте, опираясь на размеры девайсов, то можно использовать хук useResponsiveProp()
:
import {useResponsiveProp} from '@shopify/restyle';
export const MyComponent = () => {
const value = useResponsiveProp({
s: 'Сейчас S устройство',
m: 'Сейчса M устройство'
});
return <Text>{value}</Text>;
};
Использование токенов из темы
Нам не нужно импортировать нужные токены из темы, можно нужно просто достать их используя хук useTheme()
.
import {useTheme} from '@shopify/restyle';
export const MyComponent = () => {
const {colors: {g300}} = useTheme();
return <OutsideComponent color={g300}>Привет!</OutsideComponent>
};
Это может пригодиться, если нам нужно пробросить именно значение цвета в сторонний компонент.
Производительность
Я произвел исследование по производительности 3 вариантов и получил интересные результаты!
Как именно измерял
Для теста был создан следующий компонент:
![](https://habrastorage.org/getpro/habr/upload_files/779/69d/031/77969d0314fd36a2f60ef36c4228758e.png)
Компонент содержал 100 строк
В каждой строке был номер, созданный шрифтом из ДС
В каждой строке было 20 блоков, с радиусом из ДС и рандомным цветом из ДС
Проводилось 10 замеров рендера для каждого варианта, затем для результата бралось среднее значение.
Измерялось время рендера следующим образом:
export const Component = () => {
const start = new Date().getTime();
useEffect(() => {
const renderTime = new Date().getTime() - start;
console.log(renderTime);
}, []);
return (
...
);
};
Вариант UI | Время рендера (милисекунды) |
StyleSheet | 408 |
Restyle | 499 |
Styled-components | 632 |
Оказалось, что Restyle значительно быстрее Styled-components, но естественно немного медленнее вообще нативного решения, потому что содержит дополнительные обертки и парсер атрибутов в стили.
Итог
Что мы получаем от Restyle:
Удобные “строительные блоки”
Box
иText
Удобная работа с токенами ДС через атрибуты
Все токены достаются через хуки из Restyle контекста
Удобочитаемые композиции верстки сразу со стилями
Понятная и однозначная концепция для построения UI
Эта библиотека точно заслуживает внимания. Не вижу причин не брать ее на вооружение прям сейчас! Спасибо за внимание ?