Pull to refresh

О React Native

Reading time 5 min
Views 50K


Несколько советов о том, что нужно знать, чтобы писать (или не писать) приложения под React Native.

Сразу оговорюсь, что я ни разу не писал приложения под iOS, однако участвовал уже минимум в 4 проектах с React.js, немного разбираюсь в objective-c и знаком с процессом разработки под Android.

Приложение довольно простое (todo лист), но думаю, что это хороший старт.

Задача: написать таск менеджер с монетизацией. Есть наброски интерфейса на invisionapp, остальное — дело техники.

Data-flow


Самое главное в react приложениях — правильно выстроить data-flow и взаимодействие между различными компонентами с самого начала. Тогда есть вероятность, что компоненты будут только рендерить данные, а не превратятся в простыню из асинхронных вызовов и кучу логики, которую «потом можно будет вынести» куда-нибудь в другое место. На помощь нам приходит библиотека Redux. Я не буду подробно описывать как она работает (информацию можно найти здесь и здесь).

В приложении есть один store с глобальным состоянием. Он собирается из нескольких редьюсеров, обрабатывающих отдельные коллекции. Для удобства также рекомендую подключить redux-thunk и какой-нибудь devtool для отслеживания состояний стора. К сожалению, для native я ничего подобного не нашел, но это не проблема, т.к. trace состояния пишется в 10 строчек.

Код middleware
var i = 0
const devtools = store => next => action => {
  const result = next(action)
  if (console.group) {
    console.group(`#${i++}`, action.type, action)
    _(store.getState()).map((val, key) => {
      console.log(key, val)
    })
    console.groupEnd()
  }
  return result
}


Redux-thunk нужен для комбинирования нескольких асинхронных действий, а также для действий, которые требуют получение текущего состояния. Пример: Для задачи нужно создать результат, который помещается в другую коллекцию, провести оплату и пометить задачу как архивированную. Здесь участвуют 3 коллекции и 3 различных действия (все асинхронные).

Глобальное состояние позволяет нам в любой момент времени сделать snapshot стора, загрузить его на другом устройстве и увидеть то же самое, что мы видели на первом. Это упрощает процесс воспроизведения багов при краш репортах (попробуйте сделать то же самое на objective-c). Идея не новая: clojure-script обертка для react использует точно такой же подход. Есть несколько презентаций, в которых говорится о плюсах и минусах решения. Кстати, о краш репортах, я использую Crashlytics (ныне Fabric.io).

Кроме того, глобальное состояние облегчает работу с навигацией (я так думал). На самом деле пришлось городить обертку для нативного навигатора, делать индекс операций и дублировать redux действия вызовами методов в componentWillReceiveProps.

Код компонента
  componentWillReceiveProps(props) {
    const routes = props.routes

    if (this.props.routes.opIndex != routes.opIndex) {
      switch(routes.lastAction) {
      case 'push':
        this.refs.navigator.push(routes.currentRoute)
        break

      case 'pop':
        this.refs.navigator.pop()
        break

      case 'replace':
        this.refs.navigator.replace(routes.currentRoute)
        break

      case 'replacePreviousAndPop':
        this.refs.navigator.replacePrevious(routes.currentRoute)
        InteractionManager.runAfterInteractions(() => {
          this.refs.navigator.pop()
        })
        break
      }
    }
  }



Используйте promises (или await/async синтаксис ES). Если их возвращать из действий redux, то можно без проблем сделать, например, загрузчик.

Быстродействие и верстка


Верстка — один из самых приятных процессов в React Native. В Android тоже используется xml для layout'ов и он удобен, если сидеть и разбираться, что для чего служит. Однако в реакте вы уже знаете, как это работает… если вам надо сверстать hello, world :) На деле получается непредсказуемое поведение компонентов. «position: absolute» работает не так как в браузере, flux-box тоже работает по-другому, изображения не выставляют размер автоматически от ширины и высоты картинки. Получается, что вы как бы знаете что нужно писать, код выполняется, но это выглядит не так, как хотелось бы. Спасает только адекватная документация и live reload, но это все равно удобней чем верстка под андроид.

Каждый раз добавляя новый View в иерархию приходила мысль в голову: «а не долго ли это будет рендерится». Долго. Большим компонентам нужно делать экран загрузки, но facebook об этом предупреждает. Если вы собираетесь повесить какую-то логику на создание компонента (например загрузку данных из хранилища при старте приложения), обратите внимание на InteractionManager. Он позволит сначала отрендерить все, что накопилось и только потом выполнить ваши действия.

Что касается производительности приложения в целом: получается все равно немного медленнее, чем нативные приложения. Однако для большинства пользователей это будет незаметно. Если бы вы хотели нарисовать свой сайдбар (как например в airbnb), то реакт не самое подходящее решение, нужно писать нативный компонент. Хотя есть реализации, но, к сожалению, пока не в виде библиотеки.
Еще один пример: Navigator и NavigatorIOS. Первый написан на js, второй как нативный компонент. NavigatorIOS работает быстрее, если долго сидеть и смотреть на переходы между сценами :) Опять же, большинство пользователей даже не заметят разницы. Вся проблема заключается в том, что управление данными и рендеринг позиций элементов происходят в одном потоке, отсюда лаг. К счастью, мне не пришлось писать нативные компоненты, все удалось оптимизировать вызовами InteractionManager'a и постепенной загрузкой компонентов.

Обработка событий от пользователя тоже требует внимания. Если взять TextInput и сделать value/onChage в state текущего компонента, все работает быстро, но если вы начнете передавать через props родительскому компоненту и делать обработку, 100% понадобится debounce. К слову, в браузере это работает быстро. Еще одно замечание по поводу TextInput: когда iOS подставляет автозамену слова, onChageText не вызывается. Я не решил эту проблему да и, честно говоря, не пытался. Можно просто отключить autoCorrect значением false.

Сторонние библиотеки


Если нужен sidebar, menubar, какой-нибудь хитрый загрузчик изображения с прогрессом или календарь, то готовые решения есть. Есть, но их не так много, как хотелось бы. Получается выбор не очень большой. Многие компоненты выносят методы для управления, что не очень хорошо вписывается в логику рендеринга из глобального состояния. Хотелось бы больше value/onChange :)

Так или иначе библиотеки есть и мне не пришлось писать много велосипедов. К тому же репозитории постоянно обновляются, так что не забывайте делать npm update переодически в своем проекте перед тем как исправлять баг в библиотеках.

Дебаг и тестирование


С этим у реакта все замечательно. Можно запустить приложение сразу на двух устройствах (я запускал на iPhone и iPad для тестов) и при изменении, например, верстки, изменения одновременно отображаются сразу на двух устройствах. Очень удобно. Есть некоторые проблемы при дебаге на устройстве при помощи google chrome. Иногда может вылететь хром, иногда приложение не успевает подключиться к компьютеру. Последний раз он просто начал сыпать в консоль warning'и из-за того, что расходится время. Это можно пережить, ведь все-таки мобильное приложение.

Для автоматического тестирования я использовал jest (можно использовать любой другой test runner). Тестами покрывал только действия и редьюсеры. Компоненты не покрыты, потому что их много, а время ограничено. Информации по тестированию react native компонентов мало, но если сильно углубиться, то можно замокать таким образом, чтобы можно было тестировать верстку.

Заключение


Для меня, как веб-разработчика, появление react native очень облегчает разработку мобильных приложений, теперь я могу перенести свои знания в другую область, однако надо хотя бы понимать синтаксис objective-c, чтобы писать под iOS. React Native, да и сам по себе React позволяет не напрягаясь реагировать на изменяющиеся требования заказчика. Когда вы получаете новую задачу, вы скорее всего уже знаете как ее реализовать.

P.S. Итого у меня ушло чуть больше месяца на написание приложения с нуля при нулевых знаниях разработки под ios.

Скриншотик


Мастрид перед написанием приложений: facebook.github.io/react-native/docs/performance.html
Tags:
Hubs:
+11
Comments 25
Comments Comments 25

Articles