Когда мы создаем приложение с помощью React-Native, то сперва спрашиваем себя – как мы будем организовывать стилизацию своих компонентов. Сейчас существует множество различных библиотек. И почти все из них утверждают, что у них лучший перфоманс. Но давайте рассмотрим только три варианта:
StyleSheet, который React-Native предоставляет “из коробки”.
styled-components, технология, которая пришла к нам из веба.
Относительно новый способ – 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 в белый цвет, и меняться в зависимости от цветовой схемы телефона он не будет.
Однако это легко решается: мы можем подготовить тему самостоятельно. Для этого нужно совершить следующие действия:
Необходимо завести хранение выбранной темы в 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, также будут нюансы с навигацией и, возможно, что-то еще, в зависимости от контекста вашего проекта.
Ну, и наконец, использование наших конструкций. Здесь потребуется обернуть вызов 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,
},
});
};
Использование:
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 раз, чтобы исключить другие погрешности.
Для замера перфоманса я использовал два инструмента:
Поскольку @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:
StyleSheet – (123+118+120+120+121) / 5 = 120,4
UniStyles – (128+131+128+119+122) / 5 = 125,6
styled – (137+125+127+136+131) / 5 = 131,2
Вы видите, что разница невелика. Но я хочу отметить, что тест достаточно синтетический с малым объемом стилизации. И на больших экранах с большим количеством стилизованных элементов разница будет больше.
Если мы возьмем StyleSheet за 100%, то выйдет
StyleSheet – 100%
UniStyles – 104.31%
styled – 108.97%
Очевидно, что styled требуется больше времени на рендер. Styled-components просто не может работать быстрее, чем объект StyleSheet. Хотя бы потому что “под капотом” styled-components для React-Native использует библиотеку css-to-react-native, которая, насколько я могу судить по исходному коду, приводит наш css к тому же объекту StyleSheet. Поэтому, чем больше у вас стилей на элементе – тем больше будет затрачиваться времени на парсинг.
Выводы
Не бывает идеальных технологий. На мой взгляд следует придерживаться примерно таких правил:
Если у вас:
объемная тема, которую трудно менеджерить
часто меняются члены команды и/или многие из них приходят с web
ограничено время
возможно, у вас уже есть мобильная верстка для web, и вы бы хотели перенести ее в мобильное приложение.
То для вас, наверно, лучше подойдет styled-components.
Если у вас есть потребность в упрощенном менеджменте темы, но вы не готовы мириться с просадкой производительности, то стоит посмотреть в сторону UniStyles, библиотека упростит работу с темой без серьезных просадок в производительности.
Но если вы беспокоитесь о производительности, и у вас есть время на написание своей темы, то можно остановиться на решении “из коробки” React-Native.
В KODE мы долгое время использовали styled-components как стандартное решение —. для упрощения переходов разработчиков с web на react-native и обратно, из-за того, что у нас Frontend отдел объединяет web и mobile.Но в данный момент styled-components потихоньку устаревает и все чаще web-проекты стартуют с другими библиотеками для стилизации. Поэтому мы решили освежить и стандарт для React-native – перееха��и на UniStyles.
