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

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

Papu — приложение для заказа еды, предназначенное для Android, iOS и для веба
Здесь мы пользуемся React, поэтому нам надо отделить логику приложения от пользовательского интерфейса. Тут лучше всего использовать какую-нибудь систему для управления состоянием приложения вроде Redux или Mobx. Подобный ход сразу же делает логику функционирования приложения универсальной. Её, без изменений, можно использовать на разных платформах.
Визуальная часть приложения — это уже другой разговор. Для того чтобы сконструировать интерфейс приложения, нужно иметь универсальный набор примитивов, базовых строительных блоков. Они должны работать и в вебе, и в нативной среде. К сожалению, язык веба и язык нативных платформ — это разные вещи.
Например, стандартный веб-контейнер обратится к вам так:
А нативный — так:
Некоторые умные люди нашли выход из этой ситуации. Выходом стали специализированные библиотеки элементов. Одна из моих любимых — это замечательная библиотека React Native Web. Она не только берёт на себя заботу о базовых примитивах приложения, позволяя использовать компоненты React Native в вебе (не все компоненты!), но и даёт доступ к различным API React Native. Среди них —
С примитивами мы разобрались. Но нам ещё нужно связать среды для веб-разработки и для нативной разработки. В моём проекте использованы create-react-app (для веб-приложения) и скрипт инициализации React Native (для нативного приложения, без Expo). Сначала я создал один проект такой командой:
Обратите внимание на то, что в этой версии файла нет навигационных возможностей. Далее, надо скопировать все файлы с исходным кодом из папок веб-приложения и нативного приложения в папку нового унифицированного проекта.

Папки, которые нужно скопировать в новый проект
Теперь, в папке
Вот файл
Вот
Вот так я создал простой шаблон приложения и подготовил платформу для дальнейшей работы. Можете попробовать данный шаблон, заглянув в этот репозиторий.
Сейчас мы немного этот шаблон усложним, добавим систему маршрутизации/навигации.
Приложению, если только оно не состоит из одного экрана, нужна некая система навигации. Сейчас (речь идёт о сентябре 2018 года) существует лишь одна такая универсальная рабочая система, подходящая и для веба, и для нативных приложений. Речь идёт о React Router. Для веба это решение подходит хорошо, а вот в случае с React Native-проектами всё уже не так однозначно.
В React Router Native нет переходов между экранами, нет поддержки кнопки Назад (для платформы Android), нет модальных экранов, навигационных панелей и других возможностей. Другие средства навигации, вроде React Navigation, этими возможностями обладают.
Я использовал именно эту библиотеку, но вы можете подобрать что-нибудь другое. Поэтому в моём проекте за навигацию в веб-приложении отвечает React Router, а за навигацию в нативном приложении — React Navigation. Это, правда, создаёт новую проблему. Дело в том, что подходы к навигации и к передаче параметров в этих системах очень сильно различаются.
Для того чтобы сохранить дух React Native Web, используя везде подход, близкий к тому, что применяется в нативных приложениях, я подошёл к решению этой проблемы, создавая веб-маршруты и оборачивая их в HOC. Это дало мне возможность создать API, напоминающее React Navigation.
Такой подход позволил перемещаться между экранами веб-приложения, избавив меня от необходимости создавать отдельные компоненты для двух видов приложения.
Первый шаг реализации этого механизма заключается в создании объекта с описанием маршрутов для веб-приложения:
В сущности, тут представлена копия функции создания навигатора React Navigation с добавлениями специфических для React Router возможностей.
Далее, пользуясь моей вспомогательной функцией, я создаю маршруты
React Navigation, благодаря
Кроме того, в макет приложения нужно добавить компонент
Благодаря HOC нашей разработки (временно этот компонент называется
В React Navigation можно вернуться назад на
Опишем эти параметры:
Если вы хотите поэкспериментировать с представленной здесь идеей разработки универсальных приложений — я создал два шаблона.
Первый представляет собой чистую универсальную среду для разработки веб-приложений и нативных приложений.
Второй — это, в сущности, первый шаблон, который расширен за счёт моей системы для организации навигации по приложению.
Вот демонстрационное приложение papu, основанное на высказанных выше соображениях. Оно полно ошибок и тупиков, но вы можете самостоятельно его собрать, запустить его в браузере и на мобильном устройстве, и на практике получить представление о том, как всё это работает.
Сообществу React-разработчиков, безусловно, нужна универсальная библиотека для организации навигации по приложениям, так как это упростит разработку проектов, подобных тому, о котором мы говорили. Очень хорошо было бы, если бы библиотека React Navigation работала бы и в вебе (на самом деле, подобная возможность уже доступна, но работа с ней не лишена сложностей).
Уважаемые читатели! Пользуетесь ли вы возможностями React при создании универсальных приложений?


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

Papu — приложение для заказа еды, предназначенное для Android, iOS и для веба
Базовые строительные блоки приложения
Здесь мы пользуемся React, поэтому нам надо отделить логику приложения от пользовательского интерфейса. Тут лучше всего использовать какую-нибудь систему для управления состоянием приложения вроде Redux или Mobx. Подобный ход сразу же делает логику функционирования приложения универсальной. Её, без изменений, можно использовать на разных платформах.
Визуальная часть приложения — это уже другой разговор. Для того чтобы сконструировать интерфейс приложения, нужно иметь универсальный набор примитивов, базовых строительных блоков. Они должны работать и в вебе, и в нативной среде. К сожалению, язык веба и язык нативных платформ — это разные вещи.
Например, стандартный веб-контейнер обратится к вам так:
<div>Здравия желаю! Стандартный веб-контейнер к вашим услугам!</div>
А нативный — так:
<View>Привет! Я - простой контейнер в React Native</View>
Некоторые умные люди нашли выход из этой ситуации. Выходом стали специализированные библиотеки элементов. Одна из моих любимых — это замечательная библиотека 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 (
<View>
<TopNav />
{WebRoutesGenerator({ routeMap })}
<ModalContainer />
</View>
);
}
}
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 <RootStack />;
}
}
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
<View>
{WebRoutesGenerator({ routeMap })}
</View>
В сущности, тут представлена копия функции создания навигатора 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 для рендеринга этого маршрута
}
}
Кроме того, в макет приложения нужно добавить компонент
<ModalContainer />
из библиотеки react-router-modal
. Выводиться соответствующая страница будет именно там.Навигация между экранами
Благодаря HOC нашей разработки (временно этот компонент называется
NativeWebRouteWrapper
, и это, кстати, ужасное имя), мы можем использовать практически тот же набор функций, что и в React Navigation, для организации перемещения между страницами в веб-версии приложения:const { product, navigation } = this.props
<Button
onPress={navigation.navigate('ProductScreen', {id: product.id})}
title={`Go to ${product.name}`}
/>
<Button
onPress={navigation.goBack}
title="Go Back"
/>
Возврат к предыдущему экрану
В React Navigation можно вернуться назад на
n
экранов, которые находятся в навигационном стеке. Подобное в React Router недоступно, тут нет навигационного стека. Для того чтобы решить эту проблему, нам нужно импортировать в код функцию pop
собственной разработки. Вызывая её, передадим ей несколько параметров:import pop from '/NativeWebRouteWrapper/pop'
render() {
const { navigation } = this.props
return (
<Button
onPress={pop({screen: 'FirstScreen', n: 2, navigation})}
title="Go back two screens"
/>
)
}
Опишем эти параметры:
screen
— имя экрана (используемое React Router в веб-версии приложения).n
— число экранов для возврата с использованием стека (используется React Navigation).navigation
— объект, обеспечивающий навигацию.
Результаты работы
Если вы хотите поэкспериментировать с представленной здесь идеей разработки универсальных приложений — я создал два шаблона.
Первый представляет собой чистую универсальную среду для разработки веб-приложений и нативных приложений.
Второй — это, в сущности, первый шаблон, который расширен за счёт моей системы для организации навигации по приложению.
Вот демонстрационное приложение papu, основанное на высказанных выше соображениях. Оно полно ошибок и тупиков, но вы можете самостоятельно его собрать, запустить его в браузере и на мобильном устройстве, и на практике получить представление о том, как всё это работает.
Итоги
Сообществу React-разработчиков, безусловно, нужна универсальная библиотека для организации навигации по приложениям, так как это упростит разработку проектов, подобных тому, о котором мы говорили. Очень хорошо было бы, если бы библиотека React Navigation работала бы и в вебе (на самом деле, подобная возможность уже доступна, но работа с ней не лишена сложностей).
Уважаемые читатели! Пользуетесь ли вы возможностями React при создании универсальных приложений?
