- PVSM.RU - https://www.pvsm.ru -

Руководство по разработке Web-приложений на React Native

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

image [1]

Материал, перевод которого мы сегодня публикуем, представляет собой небольшое, но достаточно подробное руководство по разработке универсальных приложений с использованием React Native.

Зачем это всё?

Аббревиатура «PWA» (Progressive Web Apps [2], прогрессивные веб-приложения) сегодня у всех на слуху, этот трёхбуквенный акроним кажется прямо-таки китом в море технических терминов. Но и эта популярная технология всё ещё не лишена недостатков [3]. С такими приложениями связано немало технологических сложностей, существуют ситуации, в которых разработчик вынужден параллельно создавать нативные и веб-приложения. Вот [4] хорошая статья, в которой сравниваются PWA и нативные приложения.

Может быть, бизнесу стоит сосредоточить внимание лишь на нативных приложениях? Нет, не стоит. Это — большая ошибка. В результате логически оправданным шагом становится разработка единого универсального приложения. Это позволяет снизить затраты времени на разработку, уменьшить стоимость создания и поддержки проектов. Именно эти характеристики универсальных приложений и подвигли меня на один небольшой эксперимент.

Речь идёт об универсальном приложении-примере из сферы электронной коммерции для заказа еды. После эксперимента я создал шаблон для будущих проектов и для дальнейших исследований.

Руководство по разработке Web-приложений на React Native - 2
Papu — приложение для заказа еды, предназначенное для Android, iOS и для веба

Базовые строительные блоки приложения

Здесь мы пользуемся React, поэтому нам надо отделить логику приложения от пользовательского интерфейса. Тут лучше всего использовать какую-нибудь систему для управления состоянием приложения вроде Redux или Mobx. Подобный ход сразу же делает логику функционирования приложения универсальной. Её, без изменений, можно использовать на разных платформах.

Визуальная часть приложения — это уже другой разговор. Для того чтобы сконструировать интерфейс приложения, нужно иметь универсальный набор примитивов, базовых строительных блоков. Они должны работать и в вебе, и в нативной среде. К сожалению, язык веба и язык нативных платформ — это разные вещи.

Например, стандартный веб-контейнер обратится к вам так:

<div>Здравия желаю! Стандартный веб-контейнер к вашим услугам!</div>

А нативный — так:

<View>Привет! Я - простой контейнер в React Native</View>

Некоторые умные люди нашли выход из этой ситуации. Выходом стали специализированные библиотеки элементов. Одна из моих любимых — это замечательная библиотека React Native Web [5]. Она не только берёт на себя заботу о базовых примитивах приложения, позволяя использовать компоненты React Native в вебе (не все компоненты!), но и даёт доступ к различным API React Native. Среди них — Geolocation, Platform, Animated, AsyncStorage и многие другие. Взгляните на замечательные примеры, которые можно найти в руководстве [6] к этой библиотеке.

Шаблон

С примитивами мы разобрались. Но нам ещё нужно связать среды для веб-разработки и для нативной разработки. В моём проекте использованы create-react-app [7] (для веб-приложения) и скрипт инициализации [8] 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"
  }
}

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

Руководство по разработке Web-приложений на React Native - 3
Папки, которые нужно скопировать в новый проект

Теперь, в папке src, которая лежит в директории нового проекта, создадим два файла: App.js и App.native.js. Благодаря webpack [9] мы можем использовать расширения имён файлов для того, чтобы сообщить бандлеру о том, где какие файлы нужно использовать. Разделить 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;

Вот так я создал простой шаблон приложения и подготовил платформу для дальнейшей работы. Можете попробовать данный шаблон, заглянув в этот [10] репозиторий.

Сейчас мы немного этот шаблон усложним, добавим систему маршрутизации/навигации.

Проблемы навигации и их решения

Приложению, если только оно не состоит из одного экрана, нужна некая система навигации. Сейчас (речь идёт о сентябре 2018 года) существует лишь одна такая универсальная рабочая система, подходящая и для веба, и для нативных приложений. Речь идёт о React Router [11]. Для веба это решение подходит хорошо, а вот в случае с React Native-проектами всё уже не так однозначно.

В React Router Native нет переходов между экранами, нет поддержки кнопки Назад (для платформы Android), нет модальных экранов, навигационных панелей и других возможностей. Другие средства навигации, вроде React Navigation [12], этими возможностями обладают.

Я использовал именно эту библиотеку, но вы можете подобрать что-нибудь другое. Поэтому в моём проекте за навигацию в веб-приложении отвечает 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 [13]. Для работы с модальным экраном сначала надо добавить соответствующую опцию в объект 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 — объект, обеспечивающий навигацию.

Результаты работы

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

Первый [10] представляет собой чистую универсальную среду для разработки веб-приложений и нативных приложений.

Второй [14]  — это, в сущности, первый шаблон, который расширен за счёт моей системы для организации навигации по приложению.

Вот [15] демонстрационное приложение papu, основанное на высказанных выше соображениях. Оно полно ошибок и тупиков, но вы можете самостоятельно его собрать, запустить его в браузере и на мобильном устройстве, и на практике получить представление о том, как всё это работает.

Итоги

Сообществу React-разработчиков, безусловно, нужна универсальная библиотека для организации навигации по приложениям, так как это упростит разработку проектов, подобных тому, о котором мы говорили. Очень хорошо было бы, если бы библиотека React Navigation работала бы и в вебе (на самом деле, подобная возможность уже доступна [16], но работа с ней не лишена сложностей).

Уважаемые читатели! Пользуетесь ли вы возможностями React при создании универсальных приложений?

Автор: ru_vds

Источник [17]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/298574

Ссылки в тексте:

[1] Image: https://habr.com/company/ruvds/blog/428568/

[2] Progressive Web Apps: https://en.wikipedia.org/wiki/Progressive_Web_Apps

[3] недостатков: https://clutch.co/app-developers/resources/pros-cons-progressive-web-apps

[4] Вот: https://appinstitute.com/pwa-vs-native-apps/

[5] React Native Web: https://github.com/necolas/react-native-web

[6] руководстве: https://github.com/necolas/react-native-web/tree/master/docs/guides

[7] create-react-app: https://github.com/facebook/create-react-app

[8] скрипт инициализации: https://facebook.github.io/react-native/docs/getting-started

[9] webpack: https://webpack.js.org/

[10] этот: https://github.com/inspmoore/rnw_boilerplate

[11] React Router: https://reacttraining.com/react-router/

[12] React Navigation: https://reactnavigation.org/

[13] React Router Modal: https://github.com/davidmfoley/react-router-modal

[14] Второй: https://github.com/inspmoore/rnw_boilerplate_nav

[15] Вот: https://github.com/inspmoore/papu

[16] доступна: https://pickering.org/using-react-native-react-native-web-and-react-navigation-in-a-single-project-cfd4bcca16d0

[17] Источник: https://habr.com/post/428568/?utm_source=habrahabr&utm_medium=rss&utm_campaign=428568