React → React Native: снится ли фронтендерам мобильная разработка?

image-loader.svg

Привет! Я Виктор Ильтимиров, разработчик мобильных приложений в СберМаркете. Хочу рассказать, сложно ли переходить с React на React Native и зачем команда СберМаркета использует Reanimated.

Ранее я рассказывал об этом в докладе React → React Native Meetup | SberMarket Tech.

Содержание:


Пять причин перейти с React на React Native


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

Больше пользователей. В 2021 году количество смартфонов в мире достигло 4 млрд. Это значит, что каждый второй человек в мире использует смартфон, а с ним — десятки приложений.

Легкий старт. Не нужно сразу знать специфику и Android, и iOS и разбираться в нескольких языках программирования. А знания React помогут создать первое приложение и постепенно углубляться в детали.

Greenfield — можно писать нативные приложения, используя только JavaScript, не затрагивая нативные модули.

Brownfield — можно сделать крутое приложение с нуля, погружаясь в детали реализации конкретных платформ.

Отличия React от React Native


Чтобы лучше понять различия, сначала рассмотрим React.

  1. React app — приложение на React: компоненты, которые отдают некое дерево.
  2. Virtual DOM — средний блок, где происходит механизм реконсиляции.
  3. Render DOM — блок, который рендерит объекты JavaScript в HTML.


У React Native меняется только третий пункт: на его место встанет блок, который будет рендерить JS-объекты в нативные элементы. Соответственно, в приложении не будет нативных элементов DOM, таких как div или span, вместо них будут более универсальные обертки вида View или Text. А весь layout будет строиться на flexbox.

image-loader.svg

Рендер в React Native


При старте нативного приложения запускается отдельный поток JS, в который загружается JS bundle с кодом. JS парсит его и выполняет. Так на парсинг кода уходит слишком много времени — это общая проблема жирных SPA-приложений.

image-loader.svg

Хороший UX с такой скоростью парсинга не сделаешь. Чтобы решить эту проблему, команда Facebook выпустила специальный движок JS под названием Hermes. В нем парсинг JS-файла выполняется одновременно со сборкой приложения. Hermes сразу использует байт-код, который быстро запускается.

image-loader.svg

Hermes уже есть и на Android, и на iOS, но по умолчанию выключен, и его надо включать самостоятельно. Больше про Hermes можно узнать из материала команды Facebook.

Что такое Yoga layout


Окей, разработчик запустил JS-thread и загрузил туда bundle. Благодаря Hermes это произошло достаточно быстро. Дальше рендер React через специальный bridge вернет на нативный поток представление UI в виде дерева объектов, и нативной платформе нужно будет как-то его отрисовать.

Напомню, что верстку в React Native мы осуществляем при помощи Flexbox, а нативные платформы имеют свои механизмы layout.

image-loader.svg

Тут вступает в игру еще один специальный движок от Facebook под названием Yoga layout. Он создает разметку и под iOS, и под Android. Этот движок мало того, что преобразует верстку в понятный для нативный платформы layout, но еще и проводит его оптимизацию, а точнее «уплощение» интерфейса.

На флексах часто получается большая вложенность вьюшек, и это плохо для нативных платформ, которые, наоборот, предлагают инструменты для уменьшения этой вложенности. За уменьшение вложенности отвечает Yoga Layout, он хорошо оптимизирует верстку, хоть и не идеально.

Взаимодействие с пользователем


После всех предыдущих этапов пользователь увидел интерфейс, но одного отображения недостаточно, нужно еще с ним взаимодействовать. Например, пользователь нажмет на кнопку, и нативная платформа отловит этот момент. Чаще всего ничего не произойдет — вся логика написана на JS, поэтому ивент нажатия нужно отправить на JS-thread с логикой.

image-loader.svg

Ивент будет передан на сторону JS, там вызовется callback, который, вероятно, изменит какое-либо состояние, и реакт выполнит ререндер. На нативную платформу снова отправится обновленное представление дерева интерфейса, и всё повторится.

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

Анимации и библиотека Reanimated


Допустим, вам нужно сделать такой компонент, чтобы при передвижении слайдера менялось количество бонусов на кнопке.

image-loader.svg

Если разработчики хотят плавный интерфейс 60 fps, то вызов колбэка реакта 60 раз в секунду повесит приложение. Ведь потенциально нужно сделать реконсиляцию обновившихся компонентов и отправить их на нативный тред.

Чтобы этого избежать, можно менять состояние интерфейса на реакте, не используя для этого механизмы реакта. Один из вариантов — библиотека для анимаций Reanimated и Gesture handlers для обработки жестов.

Особенность библиотек в том, что между нативным и JS-тредом можно создавать общие переменные и обращаться к ним как с нативной стороны, так и с JS. За это отвечает JSI (Java Script Interface).

image-loader.svg

Вторая особенность — бизнес-логику для обработки значений нужно писать на JavaScript внутри специальных функций, названных ворклетами. Сами функции описываются внутри кода в реакте, но при сборке ворклеты будут выполняться синхронно в еще одном отдельном треде js.

Если проще — обновление значения и их слушание выполняет нативная платформа, а не реакт. При этом в нужные моменты можно получить текущее значение переменной на стороне реакта.

Рассмотрим пример.

export const App: React.FC = () => {
  const value = useSharedValue(5)
  return (
    
      
      
    
  )
}

Строка с useSharedValue подобна хуку useRef, только создает значение, доступное как с нативной стороны, так и со стороны js.

В другом компоненте, отвечающем за обработку жестов, надо описать изменение значения translateX по тапу пользователя. TranslateX является преобразованием значения, переданного в Slider в количество dp.

export const SliderTapHandler: React.FC = ({translateX}) => {
  const tapGestureHandler = (e: TapGestureHandlerStateChangeEvent) => {
    'worklet'
    if (e.nativeEvent.state === State.BEGAN) {
      translateX.value = withTiming(e.nativeEvent.x)
    }
  }
  return (
    
      
    
  )
}

Обратите внимание на функцию tapGestureHandler и на то, что функция начинается со специального слова «worklet», которое является триггером для babel. Тогда он знает, что функцию-ворклет нужно обработать в синхронном треде.

Функция-обертка withTiming анимирует изменение значения translateX, чтобы оно не происходило скачком.

И самая интересная часть — отображение значения sharedValue. Для этого я использовал react-native-animateable-text — он позволяет анимировать текст.

export const AnimatedText: React.FC = ({value}) => {
  const animatedProps = useAnimatedProps(() => ({
    text: `Значение value = ${value.value}`,
  }))
  return 
}

Интересной частью тут является хук useAnimatedProps, который также использует ворклеты. В нем мы преобразуем значение в строку и передаем ее в текстовый компонент для отображения данных.

Примеры в этой статье намеренно упрощены, и опущены некоторые детали реализации, полный пример я выложил на GitHub.

Цикл роликов по Reanimated от СберМаркета


Мы активно используем React Native и особенно библиотеку Reanimated. В ней у нас больше года экспертизы — мы использовали ее еще до релиза второй версии. Буду рад поделиться опытом команды СберМаркета про React Native и Reanimated, так что смело пишите в телеграм: @DeepDreamPrediction.

Сейчас мы готовим серию коротких видеороликов про компоненты на Reanimated, которые используем в СберМаркете. Ролики выйдут после Нового года на YouTube-канале SberMarket Tech. Подписывайтесь, чтобы не пропустить.


image-loader.svgМы завели соцсети с новостями и анонсами Tech-команды! Теперь можно следить за нами там, где вам удобнее всего: Telegram, VK, FB, Twitter. Подписывайтесь сами и друзей зовите

© Habrahabr.ru