Привет я Никита, занимаюсь разработкой на React/React Native и на всяких других штуках в Secreate. Хочу немного поговорить о том, что же всё-таки такое React Native, почему он сейчас важен и популярен, а также вместе с вами попробую написать небольшой проект для демонстрации основ.
Что такое React Native?
Сначала нужно разобраться, что есть React. React — это инструмент для создания пользовательских интерфейсов. Его основная задача — обеспечить отображение на экране того, что можно увидеть на веб-страницах. React позволяет легко создавать интерфейсы, разделяя каждую страницу на небольшие фрагменты и компоненты. Он очень удобен для создания веб-приложений и не требует большого порога вхождения. Так вот, разработчики популярной соцсети подумали, что было бы круто, если бы React можно было бы использовать для создания кросс-платформенных мобильных приложений, и в 2015 году React Native был представлен публике и появился на GitHub, где уже каждый мог внести свой вклад. React популярен по нескольким причинам. Он компактен и отличается высокой производительностью, в особенности при работе с быстро меняющимися данными. За счет своей компонентной структуры, React поощряет вас писать модульный и многократно используемый код.
В мире кроссплатформенной мобильной разработки уже были свои решения, к примеру Apache Cordova — технология, которая позволяла использовать HTML + CSS + JavaScript + нативный функционал платформы, на которой приложение было запущено, для его работы. Однако, технология имеет большую проблему — быстродействие. Так же на данный момент существуют и другие, такие как Xamarin, Flutter, QT и так далее.
В React Native код пишется на JavaScript, и исполняется при помощи JavaScriptCore — движка, который использует Safari. Так же можно использовать нативные модули платформы, к примеру камеру или Bluetooth. Для этого пишется код, реализующий функциональность на языке, который предназначается для разработки под конкретную платформу (Java/Swift/Objective C) и сообщается со средой JavaScript посредством bridge.
Если сравнивать с типовой разработкой для iOS и Android, React Native предлагает много других возможностей. Так как ваше приложение в основном состоит из JavaScript, можно воспользоваться многими преимуществами. Например, для того чтобы увидеть добавленные в код изменения, вы можете немедленно «обновить» приложение, не дожидаясь завершения билда.
Для большинства стандартных элементов UI есть компоненты в RN, к примеру View = UIView в iOS, так что реализовывать изыски интерфейса должно быть несложно, если есть знания React.
Почему же RN стал таким популярным?
- для написания кода используется JavaScript, в частности React;
- есть возможность быстро написать приложение под обе платформы. Меньше затраты — выгоднее бизнесу;
- большая библиотека нативных и не-нативных компонентов;
- для отладки можно использовать браузер, так же есть hot-reload для быстрого просмотра изменений. Нет необходимости пересобирать приложение при внесении изменений в код;
- для отрисовки компонентов используются нативные компоненты используемой системы (например UIImage/ImageView), соответственно производительность такого UI выше, чем при использовании webview;
- просто написать свою библиотеку для RN, использующую нативный функционал системы;
- ещё одна причиной почему RN набрал популярность в последние годы является то, что его используют такие гиганты как Skype, Tesla, Baidu, Bloomberg и т. д.
Первое приложение
Давайте попробуем написать своё первое приложение и понять что для этого нужно. Пусть это будет, к примеру, приложение для отображения и поиска разных пользователей.
Нам понадобится cli утилита npx, которая поставляется вместе с Node.js, Android Studio — если мы хотим протестировать приложение на Android, либо Xcode — если на iPhone. Как установить указанные инструменты подробнее можно почитать здесь.
Начнем с того, что создадим проект командой npx react-native init RandomGuysApp
. В RN приложение также можно добавить TypeScript, но мы этого здесь делать не будем.
Спустя какое-то время у нас создалась такая структура папок
В консоли пишем yarn add @react-navigation/native @react-navigation/stack react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
. Это установит библиотеку для навигации, что-бы можно было переходить между экранами приложения и зависимости для неё. Redux, или другую библиотеку для управления состоянием я решил не подключать, так-как думаю, что в нашем примере это будет немного излишне. После установки, если вы собираетесь запускать приложение на iOS, нужно зайти в папку ios
и написать pod install
(предварительно установив cocoapods на компьютер).
В index.js
написано:
AppRegistry.registerComponent(appName, () => App);
это показывает, что компонент App
является точкой входа в наше приложение. Я предпочитаю хранить код в отдельной папке, поэтому перенесу App.js
в новую папку — src
, сменив в index.js
путь до него на
import {App} from './src/App';
Начнем с создания роутера. В браузере, когда пользователь кликает по ссылке, URL добавляется в стек истории переходов, а когда он жмет кнопку назад — браузер удаляет последний посещенный адрес из стека. В RN по умолчанию такого понятия нет, для этого и нужен React Navigation. В приложении может быть несколько стеков. К примеру у каждого таба внутри приложения может быть собственная история посещений, но может быть и один.
Создадим папку navigators
, а в ней RootNavigator.js
, в который поместим такой код
import * as React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import {PersonListScreen} from '../screens/PersonListScreen';
const Stack = createStackNavigator();
export const RootNavigator = () => {
return (
<Stack.Navigator initialRouteName={'list'}>
<Stack.Screen name={'list'} component={PersonListScreen} />
</Stack.Navigator>
);
};
Пара моментов:
- компонента
ListScreen
еще не существует; - в приложении ожидается всего два экрана, так что структурно оно несколько упрощено, для демонстрации работы с RN.
Что здесь происходит? Создаётся новый стек навигации, в котором указывается список экранов, находящихся в нем. А также указывается стартовый экран стека. Т. к. у нас будет всего один стек, то по сути это и будет стартовый экран приложения.
Перейдем к созданию первого экрана. Добавим папку screens
, в которой создадим файл PersonListScreen
Там разместим такой код:
import React, {Component} from 'react';
import {FlatList, View, StyleSheet} from 'react-native';
import {PersonListItem} from '../components/PersonListItem';
export class PersonListScreen extends Component {
state = {
list: [],
isLoading: false,
};
componentDidMount = () => {
this.onRefresh();
};
getMoreData = (isRefreshing) => {};
onRefresh = () => {
this.getMoreData(true);
};
onScrollToEnd = ({distanceFromEnd}) => {
if (distanceFromEnd < 0) {
return;
}
this.getMoreData(false);
};
onItemPress = (item) => {
this.props.navigation.navigate('info', {person: item});
};
keyExtractor = (person) => person.login.uuid;
renderItem = ({item}) => {
return (
<PersonListItem
person={item}
onPress={this.onItemPress.bind(this, item)}
/>
);
};
render = () => {
const {isLoading, list} = this.state;
return (
<View style={styles.container}>
<FlatList
data={list}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
refreshing={isLoading}
onRefresh={this.onRefresh}
onEndReached={this.onScrollToEnd}
onEndReachedThreshold={0.2}
/>
</View>
);
};
}
Опять же, PersonListItem
еще не существует, но это временно. FlatList
, это эффективный компонент для рендера списков — он поддерживает PullToRefresh
, прокрутку к элементу списка по индексу, производительный рендер за счет отключения рендера компонентов, которые слишком далеко от видимой области экрана и многое другое. Конечно, одинаковые элементы в столбик/ряд можно рисовать и другим образом, например через ScrollView
, но это самый эффективный способ.
В renderItem
передается каждый элемент массива из пропсы data
, и возвращается списковый компонент. keyExtractor
это по сути аналог свойства key
, только в виде функции, вызываемой для каждого элемента.
Итак, при заходе на страницу мы собираемся получить список юзеров, и отрисовать его. Когда список тянется вниз — обновить его, а когда мы достигаем его конца — подгрузить новые данные. Вернёмся пока к PersonListItem
, добавим в components
файл PersonListItem.js
и заполним его таким кодом
import React, {Component} from 'react';
import {StyleSheet, TouchableOpacity, Image, Text, View} from 'react-native';
export class PersonListItem extends Component {
render = () => {
const {onPress, person} = this.props;
return (
<TouchableOpacity style={styles.container} onPress={onPress}>
<Image
source={{uri: person.picture.medium}}
resizeMode={'contain'}
style={styles.avatar}
/>
<View style={styles.col}>
<Text style={styles.name}>
{person.name.first} {person.name.last}
</Text>
<Text style={styles.email}>{person.email}</Text>
</View>
</TouchableOpacity>
);
};
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
borderBottomColor: '#b0b0b0',
borderBottomWidth: 0.4,
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
},
col: {
marginLeft: 10,
},
name: {
fontSize: 16,
color: '#2e2e2e',
},
email: {
marginTop: 10,
fontSize: 13,
color: '#b0b0b0',
},
});
В RN нет пропсы className
и стили передаются в пропсе style
, а объявляются через StyleSheet.create()
. Можно, конечно, использовать объект как стиль, но в этом случае не будут применяться некоторые внутренние оптимизации производительности.
Заметили, что вместо пикселей используются голые числа? Потому что по умолчанию, как единица измерения используются dp
(density-independent pixels), а для конвертации в px
можно использовать PixelRatio.getPixelSizeForLayoutSize()
, но это нам не особо нужно. В принципе, практически все свойства стилей дублируют CSS, так-что особо проблем быть не должно. Но есть такая особенность — все компоненты в RN имеют display: flex
. Ещё есть display: none
, других вариантов нет. Главное отличие, что все компоненты по умолчанию имеют flexDirection: column
, в отличие от браузерной версии. Есть еще несколько отличий, но перечислять я их не буду, в целом можно сказать, что в RN display: flex
несколько урезанный.
Вернёмся к App.js
. Заменим содержимое файла следующим кодом
import React, {Component} from 'react';
import {SafeAreaView} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {RootNavigator} from './navigators/RootNavigator';
export class App extends Component {
render = () => {
return (
<SafeAreaView>
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</SafeAreaView>
);
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
Используем наш созданный роутер, что-бы у нас работала навигация. Так же здесь используется компонент SafeAreaView
, он нужен что-бы контент в iOS отрисовывался внутри «безопасных» границ экрана и, к примеру, не наезжал на «монобровь» в iPhone X.
Сборка и запуск
Попробуем собрать и запустить то, что у нас есть. Если у вас macOS, то можно использовать Xcode. Для этого при открытии проекта нужно указать файл ios/RandomGuysApp.xcworkspace
внутри проекта. Если Android, то нужно установить JDK и необходимые SDK. Проще всего открыть папку android
в Android Studio, и IDE установит необходимое ПО. А после этого написать в корневой папке проекта npx react-native run-android
.
Когда проект запустится, то на экране должно появиться, что-то вроде этого.
Это экран с названием list
, который мы зарегистрировали в навигаторе. Но там пока что пусто, так как нам еще нечего отрисовывать. Поэтому вернемся в PersonListScreen.js
и добавим в функцию getMoreData()
код для получения данных.
getMoreData = (isRefreshing) => {
const limit = 15;
const offset = isRefreshing ? 0 : this.state.list.length;
const page = Math.ceil(offset / limit) + 1;
fetch(`https://randomuser.me/api/?seed=foobar&results=15&page=${page}`)
.then((r) => r.json())
.then((json) => {
this.setState({
list: isRefreshing
? this.state.list.concat(json.results)
: json.results,
});
})
.catch((e) => {
console.log(e);
});
};
Для отладки приложения можно использовать react-native-debugger, либо браузер. Включается дебаггер комбинацией клавиш ctrl(cmd) + M
для Android, cmd + D
для iOS. Там же можно включить live reload.
Сверху к import
из react-native добавим импорт модуля StyleSheet
import {FlatList, View, StyleSheet} from 'react-native';
Вниз этого же файла добавим
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
и добавим этот стиль нашему View
. Получаем что-то вроде такого
При подтяжке экрана вниз — список обновляется, при скролле — догружается. Добавим возможность перехода в карточку юзера.
onItemPress = (item) => {
this.props.navigation.navigate('info', {person: item});
};
Это переход в экран с названием info
с передачей выбранного юзера как параметра. В дальнейшем его можно будет получить из компонента экрана. Теперь добавим этот экран в навигатор RootNavigator.js
и напишем для него код.
...
import {PersonInfoScreen} from '../screens/PersonInfoScreen';
...
<Stack.Screen name={'list'} component={PersonListScreen} />
<Stack.Screen name={'info'} component={PersonInfoScreen} />
...
Так же создадим файл src/screens/PersonInfoScreen.js
и добавим туда следующий код.
import React, {Component} from 'react';
import {StyleSheet, View, Image, Text} from 'react-native';
export class PersonInfoScreen extends Component {
renderRow = (cells) => {
return cells.map((cell) => (
<View style={styles.cell} key={cell.title}>
<Text style={styles.cellTitle}>{cell.title}</Text>
<Text style={styles.cellValue}>{cell.value}</Text>
</View>
));
};
render = () => {
const {person} = this.props.route.params;
return (
<View style={styles.container}>
<Image
source={{uri: person.picture.large}}
style={styles.avatar}
resizeMode={'contain'}
/>
{this.renderRow([
{title: 'login', value: person.login.username},
{title: 'name', value: person.name.first},
{title: 'surname', value: person.name.last},
{title: 'email', value: person.email},
{title: 'phone', value: person.cell},
])}
</View>
);
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
},
avatar: {
width: 100,
height: 100,
borderRadius: 50,
marginTop: 20,
},
cell: {
marginTop: 15,
justifyContent: 'center',
alignItems: 'center',
},
cellTitle: {
fontSize: 13,
color: '#b0b0b0',
},
cellValue: {
marginTop: 10,
fontSize: 16,
color: '#2e2e2e',
},
});
Вот что у нас получилось.
Таким образом мы сделали приложение для вывода и просмотра некоторой информации по разным людям.
Ресурсы
Для изучения RN можно найти много ресурсов, вот небольшой список:
- Официальная документация, здесь вы сможете найти описания всех API и встроенных компонентов библиотеки.
- Один из лучших курсов для изучения React Native. Он поможет быстро разобраться в нём, а также научит пониманию экосистемы RN и React в целом.
- Тут большой список крутых библиотек на разные случаи жизни и статей по теме.
В итоге
React Native — это мощная платформа, используемая предприятиями любого размера для создания мобильных приложений. Это быстрый, эффективный и относительно простой способ для создания приложений на JavaScript. Главным его преимуществом является то, что он позволяет быстро создавать приложение под несколько платформ одновременно, а также имеет невысокий порог вхождения. React Native широко используется крупными брендами, поэтому не стоит беспокоиться о том, что он куда-то денется в ближайшие пару лет.
Код проекта можно найти здесь.