Руководство по разработке Web-приложений на React Native
Вы проснулись. Сияет солнце, щебечут птички. В мире никто ни с кем не воюет, никто не голодает, а один и тот же код можно использовать и в веб-проектах, и в нативных приложениях. Как бы было хорошо! К сожалению, на горизонте можно разглядеть лишь универсальный код, но путь к нему, даже сегодня, всё ещё полон неожиданностей.

Материал, перевод которого мы сегодня публикуем, представляет собой небольшое, но достаточно подробное руководство по разработке универсальных приложений с использованием React Native.
Зачем это всё?
Аббревиатура «PWA» (Progressive Web Apps, прогрессивные веб-приложения) сегодня у всех на слуху, этот трёхбуквенный акроним кажется прямо-таки китом в море технических терминов. Но и эта популярная технология всё ещё не лишена недостатков. С такими приложениями связано немало технологических сложностей, существуют ситуации, в которых разработчик вынужден параллельно создавать нативные и веб-приложения. Вот хорошая статья, в которой сравниваются PWA и нативные приложения.
Может быть, бизнесу стоит сосредоточить внимание лишь на нативных приложениях? Нет, не стоит. Это — большая ошибка. В результате логически оправданным шагом становится разработка единого универсального приложения. Это позволяет снизить затраты времени на разработку, уменьшить стоимость создания и поддержки проектов. Именно эти характеристики универсальных приложений и подвигли меня на один небольшой эксперимент.
Речь идёт об универсальном приложении-примере из сферы электронной коммерции для заказа еды. После эксперимента я создал шаблон для будущих проектов и для дальнейших исследований.

Papu — приложение для заказа еды, предназначенное для Android, iOS и для веба
Базовые строительные блоки приложения
Здесь мы пользуемся React, поэтому нам надо отделить логику приложения от пользовательского интерфейса. Тут лучше всего использовать какую-нибудь систему для управления состоянием приложения вроде Redux или Mobx. Подобный ход сразу же делает логику функционирования приложения универсальной. Её, без изменений, можно использовать на разных платформах.
Визуальная часть приложения — это уже другой разговор. Для того чтобы сконструировать интерфейс приложения, нужно иметь универсальный набор примитивов, базовых строительных блоков. Они должны работать и в вебе, и в нативной среде. К сожалению, язык веба и язык нативных платформ — это разные вещи.
Например, стандартный веб-контейнер обратится к вам так:
Здравия желаю! Стандартный веб-контейнер к вашим услугам!
А нативный — так:
Привет! Я - простой контейнер в React Native
Некоторые умные люди нашли выход из этой ситуации. Выходом стали специализированные библиотеки элементов. Одна из моих любимых — это замечательная библиотека React Native Web. Она не только берёт на себя заботу о базовых примитивах приложения, позволяя использовать компоненты React Native в вебе (не все компоненты!), но и даёт доступ к различным API React Native. Среди них — Geolocation , Platform , Animated , AsyncStorage и многие другие. Взгляните на замечательные примеры, которые можно найти в руководстве к этой библиотеке.
Шаблон
С примитивами мы разобрались. Но нам ещё нужно связать среды для веб-разработки и для нативной разработки. В моём проекте использованы create-react-app (для веб-приложения) и скрипт инициализации React Native (для нативного приложения, без Expo). Сначала я создал один проект такой командой: create-react-app rnw_web . Затем создал второй проект: react-native init raw_native . Затем я, последовав примеру Виктора Франкенштейна, взял файлы package.json из этих двух проектов и объединил их. После этого я, в папке нового проекта, скормил новый файл yarn . Вот о каком package -файле идёт речь:
< "name": "rnw_boilerplate", "version": "0.1.0", "private": true, "dependencies": < "react": "^16.5.1", "react-art": "^16.5.1", "react-dom": "^16.5.1", "react-native": "0.56.0", "react-native-web": "^0.9.0", "react-navigation": "^2.17.0", "react-router-dom": "^4.3.1", "react-router-modal": "^1.4.2" >, "devDependencies": < "babel-jest": "^23.4.0", "babel-preset-react-native": "^5", "jest": "^23.4.1", "react-scripts": "1.1.5", "react-test-renderer": "^16.3.1" >, "scripts": < "start": "node node_modules/react-native/local-cli/cli.js start", "test": "jest", "start-ios": "react-native run-ios", "start-web": "react-scripts start", "build": "react-scripts build", "test-web": "react-scripts test --env=jsdom", "eject-web": "react-scripts eject" >>
Обратите внимание на то, что в этой версии файла нет навигационных возможностей. Далее, надо скопировать все файлы с исходным кодом из папок веб-приложения и нативного приложения в папку нового унифицированного проекта.

Папки, которые нужно скопировать в новый проект
Теперь, в папке src , которая лежит в директории нового проекта, создадим два файла: App.js и App.native.js . Благодаря webpack мы можем использовать расширения имён файлов для того, чтобы сообщить бандлеру о том, где какие файлы нужно использовать. Разделить App -файлы жизненно важно, так как мы собираемся использовать разные подходы для навигации по приложениям.
Вот файл App.js , предназначенный для веба. Для навигации используется react-router .
// App.js - WEB import React, < Component >from "react"; import < View >from "react-native"; import WebRoutesGenerator from "./NativeWebRouteWrapper/index"; import < ModalContainer >from "react-router-modal"; import HomeScreen from "./HomeScreen"; import TopNav from "./TopNav"; import SecondScreen from "./SecondScreen"; import UserScreen from "./UserScreen"; import DasModalScreen from "./DasModalScreen"; const routeMap = < Home: < component: HomeScreen, path: "/", exact: true >, Second: < component: SecondScreen, path: "/second" >, User: < component: UserScreen, path: "/user/:name?", exact: true >, DasModal: < component: DasModalScreen, path: "*/dasmodal", modal: true >>; class App extends Component < render() < return ( )> ); > > export default App;
Вот App.js для React Native-приложения. Тут для навигации используется react-navigation .
// App.js - React Native import React, < Component >from "react"; import < createStackNavigator, createBottomTabNavigator >from "react-navigation"; import HomeScreen from "./HomeScreen"; import DasModalScreen from "./DasModalScreen"; import SecondScreen from "./SecondScreen"; import UserScreen from "./UserScreen"; const HomeStack = createStackNavigator( < Home: < screen: HomeScreen, navigationOptions: < title: "Home" >> >); const SecondStack = createStackNavigator( < Second: < screen: SecondScreen, navigationOptions: < title: "Second" >>, User: < screen: UserScreen, navigationOptions: < title: "User" >> >); const TabNav = createBottomTabNavigator(< Home: HomeStack, SecondStack: SecondStack >); const RootStack = createStackNavigator( < Main: TabNav, DasModal: DasModalScreen >, < mode: "modal", headerMode: "none" >); class App extends Component < render() < return ; > > export default App;
Вот так я создал простой шаблон приложения и подготовил платформу для дальнейшей работы. Можете попробовать данный шаблон, заглянув в этот репозиторий.
Сейчас мы немного этот шаблон усложним, добавим систему маршрутизации/навигации.
Проблемы навигации и их решения
Приложению, если только оно не состоит из одного экрана, нужна некая система навигации. Сейчас (речь идёт о сентябре 2018 года) существует лишь одна такая универсальная рабочая система, подходящая и для веба, и для нативных приложений. Речь идёт о React Router. Для веба это решение подходит хорошо, а вот в случае с React Native-проектами всё уже не так однозначно.
В React Router Native нет переходов между экранами, нет поддержки кнопки Назад (для платформы Android), нет модальных экранов, навигационных панелей и других возможностей. Другие средства навигации, вроде React Navigation, этими возможностями обладают.
Я использовал именно эту библиотеку, но вы можете подобрать что-нибудь другое. Поэтому в моём проекте за навигацию в веб-приложении отвечает React Router, а за навигацию в нативном приложении — React Navigation. Это, правда, создаёт новую проблему. Дело в том, что подходы к навигации и к передаче параметров в этих системах очень сильно различаются.
Для того чтобы сохранить дух React Native Web, используя везде подход, близкий к тому, что применяется в нативных приложениях, я подошёл к решению этой проблемы, создавая веб-маршруты и оборачивая их в HOC. Это дало мне возможность создать API, напоминающее React Navigation.
Такой подход позволил перемещаться между экранами веб-приложения, избавив меня от необходимости создавать отдельные компоненты для двух видов приложения.
Первый шаг реализации этого механизма заключается в создании объекта с описанием маршрутов для веб-приложения:
import WebRoutesGenerator from "./NativeWebRouteWrapper"; // функция собственной разработки, которая генерирует маршруты React Router и оборачивает их в HOC const routeMap = < Home: < screen: HomeScreen, path: '/', exact: true >, Menu: < screen: MenuScreen, path: '/menu/sectionIndex?' >> //в методе render )>
В сущности, тут представлена копия функции создания навигатора React Navigation с добавлениями специфических для React Router возможностей.
Далее, пользуясь моей вспомогательной функцией, я создаю маршруты react-router и оборачиваю их в HOC. Это позволяет клонировать компонент screen и добавить navigation в его свойства. Этот подход имитирует поведение React Navigation и делает доступными методы вроде navigate() , goBack() , getParam() .
Модальные экраны
React Navigation, благодаря createStackNavigator , даёт возможность сделать так, чтобы некая страница приложения выезжала бы снизу в виде модального экрана. Для того чтобы добиться подобного в веб-приложении, мне пришлось использовать библиотеку React Router Modal. Для работы с модальным экраном сначала надо добавить соответствующую опцию в объект routeMap :
const routeMap = < Modal: < screen: ModalScreen, path: '*/modal', modal: true //маршрутизатор будет использовать компонент ModalRoute для рендеринга этого маршрута >>
Кроме того, в макет приложения нужно добавить компонент из библиотеки react-router-modal . Выводиться соответствующая страница будет именно там.
Навигация между экранами
Благодаря HOC нашей разработки (временно этот компонент называется NativeWebRouteWrapper , и это, кстати, ужасное имя), мы можем использовать практически тот же набор функций, что и в React Navigation, для организации перемещения между страницами в веб-версии приложения:
const < product, navigation >= this.props )> title=`> /> title="Go Back" />
Возврат к предыдущему экрану
В React Navigation можно вернуться назад на n экранов, которые находятся в навигационном стеке. Подобное в React Router недоступно, тут нет навигационного стека. Для того чтобы решить эту проблему, нам нужно импортировать в код функцию pop собственной разработки. Вызывая её, передадим ей несколько параметров:
import pop from '/NativeWebRouteWrapper/pop' render() < const < navigation >= this.props return ( )> title="Go back two screens" /> ) >
Опишем эти параметры:
- screen — имя экрана (используемое React Router в веб-версии приложения).
- n — число экранов для возврата с использованием стека (используется React Navigation).
- navigation — объект, обеспечивающий навигацию.
Результаты работы
Если вы хотите поэкспериментировать с представленной здесь идеей разработки универсальных приложений — я создал два шаблона.
Первый представляет собой чистую универсальную среду для разработки веб-приложений и нативных приложений.
Второй — это, в сущности, первый шаблон, который расширен за счёт моей системы для организации навигации по приложению.
Вот демонстрационное приложение papu, основанное на высказанных выше соображениях. Оно полно ошибок и тупиков, но вы можете самостоятельно его собрать, запустить его в браузере и на мобильном устройстве, и на практике получить представление о том, как всё это работает.
Итоги
Сообществу React-разработчиков, безусловно, нужна универсальная библиотека для организации навигации по приложениям, так как это упростит разработку проектов, подобных тому, о котором мы говорили. Очень хорошо было бы, если бы библиотека React Navigation работала бы и в вебе (на самом деле, подобная возможность уже доступна, но работа с ней не лишена сложностей).
Уважаемые читатели! Пользуетесь ли вы возможностями React при создании универсальных приложений?

- React Native
- разработка
- Блог компании RUVDS.com
- Веб-разработка
- JavaScript
- ReactJS
React Native
Обновитесь до последней версии (≥ 1.0.0), чтобы испытать эту настройку.
В отличие от React, работающего внутри браузеров, React Native имеет совсем другой опыт использования. Например, нет «фокуса вкладки», вместо этого переключение с фона на приложение рассматривается как «фокус». Чтобы настроить это поведение, вы можете заменить стандартные слушатели событий focus и online в браузере на детекторы состояния приложения React Native и другие портированные нативные API, а также настроить SWR для их использования.
Пример
Глобальная настройка
Вы можете обернуть своё приложение в SWRConfig и предварительно сконфигурировать все конфигурации там
SWRConfig value= /* . */ >> > App> SWRConfig>
Настроить события focus и reconnect
Вам нужно позаботиться о нескольких конфигурациях, таких как isOnline , isVisible , initFocus и initReconnect .
isOnline и isVisible — это функции, которые возвращают логическое значение, чтобы определить, является ли приложение «активным». По умолчанию SWR придёт на помощь сделав повторную валидацию, если эти условия не выполняются.
При использовании initFocus и initReconnect необходимо также настроить кастомный провайдер кеша. Вы можете использовать пустой Map() или любое другое хранилище, которое вам нравится.
SWRConfig value= provider: () => new Map(), isOnline() /* Настройте детектор состояния сети */ return true >, isVisible() /* Настройте детектор состояния видимости */ return true >, initFocus(callback) /* Зарегистрируйте слушатель в провайдере состояния */ >, initReconnect(callback) /* Зарегистрируйте слушатель в провайдере состояния */ > >> > App /> SWRConfig>
Рассмотрим пример initFocus :
import from 'react-native' // . SWRConfig value= provider: () => new Map(), isVisible: () => return true >, initFocus(callback) let appState = AppState.currentState const onAppStateChange = (nextAppState) => /* Если оно переходит из фонового или неактивного режима в активный */ if (appState.match(/inactive|background/) && nextAppState === 'active') callback() > appState = nextAppState > // Подпишитесь на события изменения состояния приложения const subscription = AppState.addEventListener('change', onAppStateChange) return () => subscription.remove() > > >> > App> SWRConfig>
Для initReconnect требуется, чтобы некоторые сторонние библиотеки, такие как NetInfo (opens in a new tab) , подписывались на статус сети. Реализация будет аналогична приведенному выше примеру: получение функции callback и её запуск, когда сеть выходит из автономного режима, чтобы SWR смог начать ревалидацию, чтобы поддерживать ваши данные в актуальном состоянии.
Устранение неполадок¶
Вот некоторые общие проблемы, с которыми вы можете столкнуться при настройке React Native. Если вы столкнулись с чем-то, что не указано здесь, попробуйте поиск проблемы на GitHub.
Порт уже используется¶
Пакет Metro bundler работает на порту 8081. Если другой процесс уже использует этот порт, вы можете либо завершить этот процесс, либо изменить порт, который использует бандлер.
Завершение процесса на порту 8081.¶
Выполните следующую команду, чтобы найти идентификатор процесса, который прослушивает порт 8081:
sudo lsof -i :8081
Затем выполните следующие действия, чтобы завершить процесс:
В Windows вы можете найти процесс, использующий порт 8081, используя Resource Monitor и остановить его с помощью диспетчера задач.
Использование порта, отличного от 8081¶
Вы можете настроить bundler на использование порта, отличного от 8081, с помощью параметра port :
npx react-native start --port
Вам также нужно будет обновить свои приложения, чтобы загрузить пакет JavaScript с нового порта. При запуске на устройстве из Xcode это можно сделать, обновив вхождения 8081 на выбранный вами порт в файле ios/__App_Name__.xcodeproj/project.pbxproj .
Ошибка блокировки NPM¶
Если вы столкнулись с ошибкой типа npm WARN locking Error: EACCES при использовании React Native CLI, попробуйте выполнить следующее:
sudo chown -R ~/.npm sudo chown -R /usr/local/lib/node_modules
Недостающие библиотеки для React¶
Если вы добавили React Native вручную в свой проект, убедитесь, что вы включили все соответствующие зависимости, которые вы используете, такие как RCTText.xcodeproj , RCTImage.xcodeproj . Далее, двоичные файлы, созданные на основе этих зависимостей, должны быть связаны с двоичным файлом вашего приложения. Используйте раздел Linked Frameworks and Binaries в настройках проекта Xcode. Более подробные шаги приведены здесь: Linking Libraries.
Если вы используете CocoaPods, убедитесь, что вы добавили React вместе с подспецификациями в Podfile . Например, если вы используете API , и fetch() , вам нужно добавить их в ваш Podfile :
pod 'React', :path => '../node_modules/react-native', :subspecs => [ 'RCTText', 'RCTImage', 'RCTNetwork', 'RCTWebSocket', ]
Далее убедитесь, что вы выполнили pod install и что в вашем проекте с установленным React был создан каталог Pods/ . CocoaPods проинструктирует вас использовать сгенерированный файл .xcworkspace , чтобы впредь иметь возможность использовать эти установленные зависимости.
React Native не компилируется при использовании в качестве CocoaPod.¶
Существует плагин CocoaPods под названием cocoapods-fix-react-native, который обрабатывает любые потенциальные пост-исправления исходного кода из-за различий при использовании менеджера зависимостей.
Слишком длинный список аргументов: рекурсивное расширение заголовка не удалось.¶
В настройках сборки проекта User Search Header Paths и Header Search Paths — это два конфига, которые указывают, где Xcode должен искать указанные в коде заголовочные файлы #import . Для Pods, CocoaPods использует массив определенных папок по умолчанию. Убедитесь, что этот конкретный конфиг не перезаписан, и что ни одна из настроенных папок не является слишком большой. Если одна из папок будет большой, Xcode попытается рекурсивно перебрать весь каталог и в какой-то момент выдаст ошибку.
Чтобы вернуть настройки сборки User Search Header Paths и Header Search Paths к значениям по умолчанию, установленным CocoaPods, выберите запись в панели Build Settings и нажмите delete. Это приведет к удалению пользовательского переопределения и возврату к значениям по умолчанию CocoaPod.
Транспорты недоступны¶
React Native реализует полифилл для WebSockets. Эти полифиллы инициализируются как часть модуля react-native, который вы включаете в свое приложение через import React from ‘react’ . Если вы загружаете другой модуль, требующий WebSockets, например Firebase, обязательно загрузите/требуйте его после react-native:
Исключение, не реагирующее на команду Shell Command¶
Если вы столкнулись с исключением ShellCommandUnresponsiveException, таким как:
Execution failed for task ':app:installDebug'. com.android.builder.testing.api.DeviceException: com.android.ddmlib.ShellCommandUnresponsponsiveException
react-native init зависает¶
Если вы столкнулись с проблемой, когда запуск npx react-native init зависает в вашей системе, попробуйте запустить его снова в режиме verbose и обратитесь к #2797 для выявления общих причин:
npx react-native init --verbose
Когда вы отлаживаете процесс или вам нужно узнать немного больше об ошибке, вы можете использовать опцию verbose, чтобы вывести больше журналов и информации для выяснения проблемы.
Выполните следующую команду в корневом каталоге.
npx react-native run-android --verbose
Невозможно запустить менеджер пакетов react-native (на Linux)¶
Случай 1: Ошибка «code»: «ENOSPC», «errno»: «ENOSPC».¶
Проблема вызвана количеством директорий, которые может контролировать inotify (используется watchman в Linux). Чтобы решить эту проблему, выполните в окне терминала следующую команду
fs.inotify.max_user_watches sudo tee -a /etc/sysctl.conf sudo sysctl -p
Ошибка: spawnSync ./gradlew EACCES¶
Если вы столкнулись с проблемой, когда выполнение команды npm run android на macOS приводит к вышеуказанной ошибке, попробуйте выполнить команду sudo chmod +x android/gradlew , чтобы сделать файлы gradlew исполняемыми.
Как запустить чужой проект?

Добрый день . хотел скачать и запустить чужой проект, чтобы рассмотреть его функции. но столкнулся с рядом проблем . что он не изволяет запускаться , и просто не знаю что делать, помогите пожалуйста
___
https://github.com/sunlight3d/. ialProject
1) в рабочую папку скопировал tutorialProject
2) открыл VS code
3) зашел в рабочую папку и нажал открыть в cmd папку tutorialProject
4) далее я ввожу react-native run-android
и получаю фиаско
d:\JS\tutorialProject>react-native init tutorialProject
internal/modules/cjs/loader.js:657
throw err;
^
Error: Cannot find module ‘graceful-fs’
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:655:15)
at Function.Module._load (internal/modules/cjs/loader.js:580:25)
at Module.require (internal/modules/cjs/loader.js:711:19)
at require (internal/modules/cjs/helpers.js:14:16)
at Object. (d:\JS\tutorialProject\node_modules\react-native\local-cli\cli.js:12:1)
at Module._compile (internal/modules/cjs/loader.js:805:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:816:10)
at Module.load (internal/modules/cjs/loader.js:672:32)
at tryModuleLoad (internal/modules/cjs/loader.js:612:12)
at Function.Module._load (internal/modules/cjs/loader.js:604:3)
начал гуглить и нашел что надо сделать какието зависимости . посредством «react-native init MyProject»
5) react-native init tutorialProject
и получаю туже самую строчку .
пытаюсь через npm сервер запустить и тут прикол .
6) npm intall
d:\JS\tutorialProject>npm install
npm WARN deprecated core-js@1.2.7: core-js@ npm WARN deprecated connect@2.30.2: connect 2.x series is deprecated
npm ERR! path d:\JS\tutorialProject\node_modules\.bin\react-native
npm ERR! code EEXIST
npm ERR! Refusing to delete d:\JS\tutorialProject\node_modules\.bin\react-native: is outside d:\JS\tutorialProject\node_modules\react-native and not a link
npm ERR! File exists: d:\JS\tutorialProject\node_modules\.bin\react-native
npm ERR! Move it away, and try again.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Nick\AppData\Roaming\npm-cache\_logs\2019-04-16T18_15_13_614Z-debug.log