Когда мы создаем приложение с помощью React-Native, то сперва спрашиваем себя – как мы будем организовывать стилизацию своих компонентов. Сейчас существует множество различных библиотек. И почти все из них утверждают, что у них лучший перфоманс. Но давайте рассмотрим только три варианта:

  1.  StyleSheet, который React-Native предоставляет “из коробки”.

  2.  styled-components, технология, которая пришла к нам из веба.

  3. Относительно новый способ – UniStyles.

Меня зовут Алексей Цуцоев, сейчас я Frontend TL в KODE. Эту статью я писал, когда только устроился в компанию и мне нужно было систематизировать навыки, передать свой опыт стажерам и просто высказать свои мысли относительно styled. Вижу, что до сих пор многие начинающие разработчики сталкиваются с такой же проблемой, как я в прошлом, поэтому статья вышла из нашей внутренней конфы в мир. 

В этой статье расскажу, какие преимущества и недостатки у каждой из библиотек. Публикация будет полезная тем, кто хочет освежить свои знания в React-Native или только начал их изучение этого стека.

StyleSheet

C ним все просто: мы имеем знакомый каждому react-native разработчику css-подобный синтаксис. Что-то похожее на:

const styles = StyleSheet.create({

 container: {

   flex: 1,

   paddingVertical: 20,

   paddingHorizontal: 20,

 },

 item: {

   height: 20,

   width: '100%',

   borderRadius: 10,

   marginBottom: 10,

 },

});

Все достаточно просто и удобно. Данный метод поддерживает типизацию, и вы всегда можете видеть, какие объекты стилей создали.

Также доступно соединение стилей с помощью массива:

style={[styles.item, condition && styles.extraItemStyles]}

Но с этим подходом есть проблема: использование темы.

Предположим, мы хотим добавить пользователю возможность выбора темы, или чтобы она устанавливалась по умолчанию в зависимости от выбранной на устройстве. Тут у нас возникают проблемы: если мы написали backgroundColor: ‘white’, то мы уже “покрасили” нашу View в белый цвет, и меняться в зависимости от цветовой схемы телефона он не будет.

Однако это легко решается: мы можем подготовить тему самостоятельно. Для этого нужно совершить следующие действия: 

  1. Необходимо завести хранение выбранной темы в MMKV или async-storage. Там будет уместно хранить enam из трех полей: os, dark, light

    В случае с MMKV у нас получиться что-то похожее на:

/**

* Possible variants for theme

*/

export type TAppearanceVariant = 'dark' | 'light' | 'system';


export type TAppearanceStorage = {

 get: () => TAppearanceVariant | undefined;

 set: (newValue: TAppearanceVariant) => void;

 reset: () => void;

};


const storage = new MMKV({

 id: STORAGE_ID,

});


export const appearanceStorage: TAppearanceStorage = {

 get: () => {

   const result = storage.getString(STORAGE_KEY) as

     | TAppearanceVariant

     | undefined;


   if (!isValueAppearanceVariant(result)) {

     return;

   }


   return result;

 },

 set: (newValue: TAppearanceVariant) => storage.set(STORAGE_KEY, newValue),

 reset: () => {

Важно: не стоит копировать код из примеров выше, так как он был написал схематично и исключительно для примеров. В жизни все будет немного сложнее. Придется еще обновлять цвета для навигационного бара на Android, также будут нюансы с навигацией и, возможно, что-то еще, в зависимости от контекста вашего проекта.

  1. Ну, и наконец, использование наших конструкций. Здесь потребуется обернуть вызов StyleSheet.create в функцию и добавить в аргументы тему:

export const getStyles = (theme: Theme) => {

 return StyleSheet.create({

   container: {

     flex: 1,

     backgroundColor: theme.background,

     paddingVertical: 20,

     paddingHorizontal: 20,

   },

   item: {

     height: 20,

     width: '100%',

     backgroundColor: theme.primary,

     borderRadius: 10,

     marginBottom: 10,

   },

 });

};

  1. Использование: 

const appear = useAppearanceController({ storage });

const theme = useTheme();

const styles = useMemo(() => getStyles(theme), [theme]);

Повторюсь, все это схематичный код, который написан для статьи, не стоит использовать это, как рабочее решение для прода.Но вы можете взять эту схему для своего решения и расширить его. Допустим, добавить спейсинги в объект темы, чтобы была возможность менять их в зависимости от нее. Или же добавить скейлинг, чтобы наше приложение одинаково хорошо смотрелось на разных девайсах.

Но с этой системой тоже есть проблема: это кастомная система, вам самостоятельно придется поддерживать ее и, если в команду придет новый разработчик, объяснять что и как работает. Возможно, документирование кода поможет, но не до конца. И многие разработчики сказали бы: “и зачем нам это писать, когда есть готовые решения?”. И будут правы. 

UniStyles

Тут будет все коротко. UniStyles – новый способ типизации для вашего React-Native проекта. API для работы очень похож на дефолтный StyleSheet.Вы даже можете начать просто с конфигурации библиотеки и замены импортов с 

```
import { StyleSheet } from ‘react-native’

``
На
`
import { StyleSheet } from ‘react-native-unistyles’
``

И все, вы подключили UniStyles к своему проекту. Но советую все таки ознакомиться с документацией.Библиотека дает одно важное преимущество: она позволяет упорядочить работу с темой. То есть весомую часть кода из предыдущей главы вы можете просто удалить и оставить только storage для хранения выбранной темы и ваши проект-специфичные настройки.Но UniStyles относительно новый инструмент.Возможно стоит обратиться к чему-то проверенному.Поэтому рассмотрим следующий вариант: styled-components

styled-components

Эта библиотека знакома многим web-разработчикам, думаю, в представлении она не нуждается. У нее есть адаптация под react-native, есть уже “из коробки” управление темой, и выглядит это примерно так:

const Container = styled.View`

 background-color: ${({ theme }) => theme.palette.background.primary};

 border-top-left-radius: 16px;

 border-top-right-radius: 16px;

 height: 100%;

`;

И, на первый взгляд, использование styled-components выглядит идеальным решением: есть возможность использования темы сразу, ваши новые члены команды, скорее всего, уже знакомы с этой библиотекой, а также есть возможность добавления аргументов через дженерик-параметры. Если в вашей компании нет деления на react и react-native, и web-разработчик может попасть на мобильный проект, то ему это так же будет удобно, – ведь он уже знаком с технологией. Кроме того, в верстке <Container> выглядит приятнее чем <View style={styles.container}>.

Но так же мы знаем, что styled-components – это достаточно тяжелая библиотека, и много у кого есть к ней вопросы по производительности. Кроме того, в react-native у нас нет css-class, и мы не можем для оптимизации использовать классы внутри одного компонента. Нам приде��ся создавать стилизованный объект под каждый наш компонент.

И в этот момент встает вопрос о производительности. Я засетапил простой проект, куда завез все три библиотеки и провел небольшие тесты скорости монтирования элементов.

Производительность

Я решил проверить насколько сильно влияет использование styled на производительность в react-native. Для этого я взял три тестовых экрана для каждой из библиотек. Все экраны просто рисовали 500 красных прямоугольников.

Чтобы избежать оптимизаций FlatList я использовал обычный ScrollView. Все замеры я проводил с перезапуском приложения, чтобы исключить влияние кэша, и по 5 раз, чтобы исключить другие погрешности.

Для замера перфоманса я использовал два инструмента:

  1. @shopify/react-native-performance.

  2. Поскольку @shopify/react-native-performance уже в архиве я добавил замеры через кастомное решение:
    const getNow = () => {

    if (typeof performance !== 'undefined') {

    const perfResult = performance.now();

    console.log('performance.now()', perfResult);

     return perfResult;

     }

    const dateResult = Date.now();

    console.log('Date.now()', dateResult);

    return dateResult;

    };

    const nextFrame = () =>

     new Promise<void>(resolve => requestAnimationFrame(() => resolve()));

    const measureAsync = async (fn: () => void) => {

    const start = getNow();

    fn();

    await nextFrame();

    return getNow() - start;

    };

Замеры велись в миллисекундах.

Забегая вперед – разница в замерах была минимальная, так что дальше будут цифры только через код выше (просто потому что я его добавил позже).

Ниже средние результаты (5 замеров) для рендера массива из 500 элементов для ios:

  1. StyleSheet – (123+118+120+120+121) / 5 = 120,4

  2. UniStyles – (128+131+128+119+122) / 5 = 125,6

  3. styled – (137+125+127+136+131) / 5 = 131,2

Вы видите, что разница невелика. Но я хочу отметить, что тест достаточно синтетический с малым объемом стилизации. И на больших экранах с большим количеством стилизованных элементов разница будет больше.

Если мы возьмем StyleSheet за 100%, то выйдет

  1. StyleSheet – 100%

  2. UniStyles – 104.31%

  3. styled – 108.97%

Очевидно, что styled требуется больше времени на рендер. Styled-components просто не может работать быстрее, чем объект StyleSheet. Хотя бы потому что “под капотом” styled-components для React-Native использует библиотеку css-to-react-native, которая, насколько я могу судить по исходному коду, приводит наш css к тому же объекту StyleSheet. Поэтому, чем больше у вас стилей на элементе – тем больше будет затрачиваться времени на парсинг.

Выводы

Не бывает идеальных технологий. На мой взгляд следует придерживаться примерно таких правил:
Если у вас: 

  1. объемная тема, которую трудно менеджерить

  2. часто меняются члены команды и/или многие из них приходят с web

  3. ограничено время

  4. возможно, у вас уже есть мобильная верстка для web, и вы бы хотели перенести ее в мобильное приложение.

То для вас, наверно, лучше подойдет styled-components.

Если у вас есть потребность в упрощенном менеджменте темы, но вы не готовы мириться с просадкой производительности, то стоит посмотреть в сторону UniStyles, библиотека упростит работу с темой без серьезных просадок в производительности.

Но если вы беспокоитесь о производительности, и у вас есть время на написание своей темы, то можно остановиться на решении “из коробки” React-Native.

В KODE мы долгое время использовали styled-components как стандартное решение —.  для упрощения переходов разработчиков с web на react-native и обратно, из-за того, что у нас Frontend отдел объединяет web и mobile.Но в данный момент styled-components потихоньку устаревает и все чаще web-проекты стартуют с другими библиотеками для стилизации. Поэтому мы решили освежить и стандарт для React-native – перееха��и на UniStyles.