Привет! Я Виктор Ильтимиров, разработчик мобильных приложений в СберМаркете. Хочу рассказать, сложно ли переходить с 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.



Рендер в 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, FB, Twitter. Подписывайтесь сами и друзей зовите ????

Комментарии (6)


  1. namikiri
    27.12.2021 10:16
    +3

    Мы активно используем React Native и особенно библиотеку Reanimated.

    А лучше бы использовали Kotlin и Swift.


    1. Ilya81
      27.12.2021 10:27

      Хорошо, если они совсем не вымрут, но пока к этому всё идёт.


      1. avdosev
        27.12.2021 13:46

        А есть хоть одна причина им умирать? Есть живое комьюнити, да и количество вакансий не падает, думаю, на взгляд почти любого, это явно не движение в сторону вымирания


        1. Ilya81
          27.12.2021 14:15
          -1

          По моим наблюдениям доля проектов на Cordova стремительно растёт, как и всего прочего на JavaScript, на объективность, конечно, не претендую.


      1. Prostor9
        27.12.2021 14:16
        +2

        Swift вымрет только с Эпл. Котлин тоже далек от смерти, а React Native уже в полумертвом состоянии, если верить статьям на хабре


  1. arthurg91
    27.12.2021 12:43
    +1

    Когда только был первый релиз приложения сразу было понятно что это RN) Было очень глючно... сейчас стало по лучше...

    Благодарю за статью )