Привет! Я Виктор Ильтимиров, разработчик мобильных приложений в СберМаркете. Хочу рассказать, сложно ли переходить с React на React Native и зачем команда СберМаркета использует Reanimated.
Ранее я рассказывал об этом в докладе React → React Native Meetup | SberMarket Tech.
Содержание:
- Причины переходить c React на React Native
- Отличия React от React Native
- Как происходит рендер в React Native
- Пишем анимации с помощью Reanimated
Пять причин перейти с React на React Native
Попробовать новый UX. UX мобильных приложений радикально отличается от веба. Можно даже сказать, что он удобнее и привычнее для пользователей. В СберМаркете большинство заказов идет именно с мобильного приложения, и только потом веб. Многие приложения вообще обходятся без веб-версии.
Больше пользователей. В 2021 году количество смартфонов в мире достигло 4 млрд. Это значит, что каждый второй человек в мире использует смартфон, а с ним — десятки приложений.
Легкий старт. Не нужно сразу знать специфику и Android, и iOS и разбираться в нескольких языках программирования. А знания React помогут создать первое приложение и постепенно углубляться в детали.
Greenfield — можно писать нативные приложения, используя только JavaScript, не затрагивая нативные модули.
Brownfield — можно сделать крутое приложение с нуля, погружаясь в детали реализации конкретных платформ.
Отличия React от React Native
Чтобы лучше понять различия, сначала рассмотрим React.
- React app — приложение на React: компоненты, которые отдают некое дерево.
- Virtual DOM — средний блок, где происходит механизм реконсиляции.
- Render DOM — блок, который рендерит объекты JavaScript в HTML.
У React Native меняется только третий пункт: на его место встанет блок, который будет рендерить JS-объекты в нативные элементы. Соответственно, в приложении не будет нативных элементов DOM, таких как div или span, вместо них будут более универсальные обертки вида View или Text. А весь layout будет строиться на flexbox.
Рендер в React Native
При старте нативного приложения запускается отдельный поток JS, в который загружается JS bundle с кодом. JS парсит его и выполняет. Так на парсинг кода уходит слишком много времени — это общая проблема жирных SPA-приложений.
Хороший UX с такой скоростью парсинга не сделаешь. Чтобы решить эту проблему, команда Facebook выпустила специальный движок JS под названием Hermes. В нем парсинг JS-файла выполняется одновременно со сборкой приложения. Hermes сразу использует байт-код, который быстро запускается.
Hermes уже есть и на Android, и на iOS, но по умолчанию выключен, и его надо включать самостоятельно. Больше про Hermes можно узнать из материала команды Facebook.
Что такое Yoga layout
Окей, разработчик запустил JS-thread и загрузил туда bundle. Благодаря Hermes это произошло достаточно быстро. Дальше рендер React через специальный bridge вернет на нативный поток представление UI в виде дерева объектов, и нативной платформе нужно будет как-то его отрисовать.
Напомню, что верстку в React Native мы осуществляем при помощи Flexbox, а нативные платформы имеют свои механизмы layout.
Тут вступает в игру еще один специальный движок от Facebook под названием Yoga layout. Он создает разметку и под iOS, и под Android. Этот движок мало того, что преобразует верстку в понятный для нативный платформы layout, но еще и проводит его оптимизацию, а точнее «уплощение» интерфейса.
На флексах часто получается большая вложенность вьюшек, и это плохо для нативных платформ, которые, наоборот, предлагают инструменты для уменьшения этой вложенности. За уменьшение вложенности отвечает Yoga Layout, он хорошо оптимизирует верстку, хоть и не идеально.
Взаимодействие с пользователем
После всех предыдущих этапов пользователь увидел интерфейс, но одного отображения недостаточно, нужно еще с ним взаимодействовать. Например, пользователь нажмет на кнопку, и нативная платформа отловит этот момент. Чаще всего ничего не произойдет — вся логика написана на JS, поэтому ивент нажатия нужно отправить на JS-thread с логикой.
Ивент будет передан на сторону JS, там вызовется callback, который, вероятно, изменит какое-либо состояние, и реакт выполнит ререндер. На нативную платформу снова отправится обновленное представление дерева интерфейса, и всё повторится.
В процессе разработки это будет незаметно. Программист в привычном для себя стиле будет писать интерфейс на реакте, и он будет отображаться на мобильном устройстве.
Анимации и библиотека Reanimated
Допустим, вам нужно сделать такой компонент, чтобы при передвижении слайдера менялось количество бонусов на кнопке.
Если разработчики хотят плавный интерфейс 60 fps, то вызов колбэка реакта 60 раз в секунду повесит приложение. Ведь потенциально нужно сделать реконсиляцию обновившихся компонентов и отправить их на нативный тред.
Чтобы этого избежать, можно менять состояние интерфейса на реакте, не используя для этого механизмы реакта. Один из вариантов — библиотека для анимаций Reanimated и Gesture handlers для обработки жестов.
Особенность библиотек в том, что между нативным и JS-тредом можно создавать общие переменные и обращаться к ним как с нативной стороны, так и с JS. За это отвечает JSI (Java Script Interface).
Вторая особенность — бизнес-логику для обработки значений нужно писать на JavaScript внутри специальных функций, названных ворклетами. Сами функции описываются внутри кода в реакте, но при сборке ворклеты будут выполняться синхронно в еще одном отдельном треде js.
Если проще — обновление значения и их слушание выполняет нативная платформа, а не реакт. При этом в нужные моменты можно получить текущее значение переменной на стороне реакта.
Рассмотрим пример.
export const App: React.FC = () => {
const value = useSharedValue(5)
return (
<CenterView>
<Slider min={0} max={100} step={1} value={value} />
<AnimatedText value={value} />
</CenterView>
)
}
Строка с useSharedValue подобна хуку useRef, только создает значение, доступное как с нативной стороны, так и со стороны js.
В другом компоненте, отвечающем за обработку жестов, надо описать изменение значения translateX по тапу пользователя. TranslateX является преобразованием значения, переданного в Slider в количество dp.
export const SliderTapHandler: React.FC<Props> = ({translateX}) => {
const tapGestureHandler = (e: TapGestureHandlerStateChangeEvent) => {
'worklet'
if (e.nativeEvent.state === State.BEGAN) {
translateX.value = withTiming(e.nativeEvent.x)
}
}
return (
<TapGestureHandler onHandlerStateChange={tapGestureHandler}>
<TapGestureField />
</TapGestureHandler>
)
}
Обратите внимание на функцию tapGestureHandler и на то, что функция начинается со специального слова ‘worklet’, которое является триггером для babel. Тогда он знает, что функцию-ворклет нужно обработать в синхронном треде.
Функция-обертка withTiming анимирует изменение значения translateX, чтобы оно не происходило скачком.
И самая интересная часть — отображение значения sharedValue. Для этого я использовал react-native-animateable-text — он позволяет анимировать текст.
export const AnimatedText: React.FC<Props> = ({value}) => {
const animatedProps = useAnimatedProps(() => ({
text: `Значение value = ${value.value}`,
}))
return <AnimateableText animatedProps={animatedProps} />
}
Интересной частью тут является хук useAnimatedProps, который также использует ворклеты. В нем мы преобразуем значение в строку и передаем ее в текстовый компонент для отображения данных.
Примеры в этой статье намеренно упрощены, и опущены некоторые детали реализации, полный пример я выложил на GitHub.
Цикл роликов по Reanimated от СберМаркета
Мы активно используем React Native и особенно библиотеку Reanimated. В ней у нас больше года экспертизы — мы использовали ее еще до релиза второй версии. Буду рад поделиться опытом команды СберМаркета про React Native и Reanimated, так что смело пишите в телеграм: @DeepDreamPrediction.
Сейчас мы готовим серию коротких видеороликов про компоненты на Reanimated, которые используем в СберМаркете. Ролики выйдут после Нового года на YouTube-канале SberMarket Tech. Подписывайтесь, чтобы не пропустить.
Мы завели соцсети с новостями и анонсами Tech-команды! Теперь можно следить за нами там, где вам удобнее всего: Telegram, VK. Подписывайтесь сами и друзей зовите ?