Restyle как новый стандарт для создания UI в React Native

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Меня зовут Павленко Виталий, я Team Lead в команде UI-kit в Профи.

В какой-то момент мы хотели решить несколько проблем с UI-kit в наших React Native приложениях:

  1. UI-kit был слишком медленный. И здесь важно уточнить, что он был сделан с использованием styled-components.

  2. В UI-kit не было возможности темизации, потому что токены импортировались как модуль в каждом компоненте. И как следствие, в приложении невозможно было сделать темную тему, потому что при смене токенов оно бы просто не перерендерилось.

Спойлер. 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.

Getting started | Restyle

Restyle предоставляет набор инструментов, которые позволят строить UI продуктов на основе токенов. Особенно очень хорошо такой подход залетает, когда в продукте есть Дизайн Система и этот набор токенов действительно определен.

Одно из основных преимуществ библиотеки Restyle по сравнению с той же styled-system, в том, что она не использует styled-components под капотом. В основе Restyle используются абстрактные стили StyleSheet, поэтому ожидается более высокая производительность.

Немного про токены

Что мы понимаем под токенами? Это цвета, отступы, радиусы и так далее – все строгие ограничения, касающиеся стилей компонентов.

У нас будет несколько групп токенов:

Цвета
{
  "profiBrand": "rgba(224, 25, 53, 1)",
  "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 вариантов и получили интересные результаты!

Как именно измерили

Для теста был создан следующий компонент:

  • Компонент содержал 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 

Эта библиотека точно заслуживает внимания. Не вижу причин не брать ее на вооружение прям сейчас! Спасибо за внимание

Источник: https://habr.com/ru/articles/763982/


Интересные статьи

Интересные статьи

Хочу поделиться самыми интересными трендами января, которые я увидел с помощью Трендовика — моего сервиса мониторинга новых растущих тем.
Привет! В феврале я писал про прототип плацкартного вагона в габарите 1-ВМ, и тогда пост собрал 548 комментариев. ТМХ все их прочитали, как и кучу других комментариев в других мес...
Чаще всего при выборе этого языка ожидается, что разработка одного приложения под две платформы займёт в два раза меньше времени, чем разработка двух приложений. Но по итогу оказывается, что разр...
Наверняка многие слышали понятие MVP (Минимально жизнеспособный продукт вики). На хабре тоже много статей про MVP, но в основном это или просто описание что такое MVP и для чего оно, или различны...
Вечером 22 апреля 2019 года в штаб-квартире Tesla в Пало-Альто, округе Санта-Клара, штат Калифорния, прошло специальное мероприятие для инвесторов «Tesla Autonomy Investor Day», на котором (п...