Подпишитесь на мою рассылку, если вам понравилась эта статья.
Одним из преимуществ React Native является совместное использование кода между веб, iOS и Android — «бесшовная кроссплатформенность», как говорится на главной странице. К сожалению, React Native дает нам очень мало инструментов для написания компонентов, которые работают и в вебе, и в нативе, и этот опыт далеко не бесшовный.
Проблемы кроссплатформенной разработки для React Native
Основными препятствиями для написания кроссплатформенных компонентов с помощью React Native являются:
- Разные элементы для web и native: в web мы используем
p
иdiv
, тогда как в native мы должны использоватьText
иView
из пакетаreact-native
. React Native также придирчив к рендерингу текста: мы всегда должны оборачивать его в компонентText
, и он должен быть прямым родителем. - Непростительная стилизация: в React Native существует собственный способ создания стилей, который выглядит как CSS, но не ведет себя как CSS. В CSS, если браузер не понимает определенное свойство, он будет его игнорировать, но React Native выбросит исключение, и он поддерживает очень ограниченное количество свойств CSS.
Стилизированные компоненты решают некоторые проблемы на низком уровне: прежде всего, они позволяют нам использовать один и тот же синтаксис для написания стилей для web и native. Однако это не решает проблему взлома на неподдерживаемых свойствах.
Другая проблема — медлительность и в целом плохой опыт разработчиков в эмуляторах: iOS и особенно Android. Разрабатывать пользовательские интерфейсы с помощью эмуляторов гораздо сложнее и медленнее, чем в браузере на настольном компьютере.
Возможные решения
Мой текущий подход заключается в том, чтобы разрабатывать в настольном веб-браузере, а затем тестировать React Native на эмуляторах и реальных устройствах.
Это также позволяет мне использовать для сквозных тестов ту же настройку, что и для веб: Cypress и библиотеку тестирования Cypress, которая быстро запускается и проста в написании и отладке. Затем я использую сквозные тесты с эмуляторами только для дымовых тестов или функциональности, которая сильно отличается на родных платформах.
Ниже приведены мои решения по разработке кроссплатформенных компонентов для web и React Native, от лучшего к худшему.
Примитивные компоненты
Примитивные компоненты решают множество проблем и отлично подходят для кроссплатформенной разработки. Имея компоненты для верстки, типографики, элементов пользовательского интерфейса и так далее, мы можем инкапсулировать весь специфичный для платформы код в эти компоненты, а потребителю больше не нужно заботиться о поддержке React Native:
<Stack gap="medium">
<Heading>Do or do not</Heading>
<Paragraph>There is no try</Paragraph>
<Button>Try nonetheless</Button>
</Stack>
Для потребителя не имеет значения, что Stack
имеет совершенно разные реализации для web и React Native, и что Heading
и Paragraph
отображаются с помощью разных элементов. API одинаковы, а реализация скрыта.
Использование примитивных компонентов вместо пользовательских стилей — мой любимый способ создания пользовательских интерфейсов за последние несколько лет, и в большинстве случаев он хорошо работает для кроссплатформенных интерфейсов. Это дает нам максимально чистую разметку и ограничения системы дизайна (ограничивает наш выбор интервалов, шрифтов, размеров, цветов и так далее теми, которые поддерживаются системой дизайна).
Примечание: у меня есть опыт работы только со styled-system, которая не поддерживает React Native по умолчанию и не обновлялась два года. Возможно, сейчас есть лучшее решение, и я хотел бы узнать о нем!
Я реализовал очень примитивную поддержку React Native, сохраняя только первое значение (для самого узкого экрана) отзывчивых реквизитов. Так что код выглядит следующим образом:
<Box width={[1, 1/2, 1/4]}>...</Box>
будет отображаться на React Native следующим образом:
<Box width={1}>...</Box>
Это не идеально, но пока работает нормально.
Объект Elements
Настройка HTML-элементов компонентов — распространенная практика для написания семантической разметки. Наиболее распространенным способом сделать это является использование свойства as
в styled-components, что потребует разделения кода для кросс-платформенной работы, поскольку на React Native все HTML-элементы должны быть заменены на компоненты View
и Text
:
// Web
<Stack as="form">...</Stack>
// React Native
import {View} from 'react-native';
<Stack as={View}>...</Stack>
Та же проблема, когда мы используем фабрику стилизованных компонентов:
// Web
const Heading = styled.p`...`;
// React Native
import {Text} from 'react-native';
const Heading = styled(Text)`...`;
Один из способов решения этой проблемы — создать объект с отображением элементов как для web, так и для React Native, а затем использовать его вместо строковых литералов:
// elements.ts
export const Elements = {
div: 'div',
h1: 'h1',
h2: 'h2',
h3: 'h3',
h4: 'h4',
h5: 'h5',
h6: 'h6',
header: 'header',
footer: 'footer',
main: 'main',
aside: 'aside',
p: 'p',
span: 'span',
} as const;
// elements.native.ts
import { View, Text } from 'react-native';
export const Elements = {
div: View,
h1: Text,
h2: Text,
h3: Text,
h4: Text,
h5: Text,
h6: Text,
header: View,
footer: View,
main: View,
aside: View,
p: Text,
span: Text,
} as const;
// Cross-platform component
import {Elements} from './elements';
<Stack as={Elements.form}>...</Stack>
Это немного более многословно, но код разделяется на более низком уровне и только один раз, нам не нужно разделять код каждого компонента и дублировать код.
Идея: Теперь я думаю, что лучшим способом будет инкапсуляция отображения внутри примитивных компонентов и фабрики стилизованных компонентов, так что мы можем продолжать писать as="form"
или styled.form
, и они будут прозрачно преобразованы в правильные элементы для React Native. Я еще не пробовал, но думаю, что эта идея заслуживает внимания.
Расщепление кода
Расщепление кода всегда должно быть последним средством, когда лучшие варианты недоступны. Однако при минимально возможном уровне оно все же может стать хорошим решением, особенно когда нам нужно использовать некоторые API, специфичные для конкретной платформы.
Чтобы разделить код между web и native, мы можем использовать специфические для платформы расширения:
// Link.tsx
export const Link = ({href, children}) =>
<a href={href}>{children}</a>
// Link.native.tsx
import { Text, Linking, TouchableWithoutFeedback } from 'react-native';
export const Link = ({href, children}) =>
<TouchableWithoutFeedback onPress={() => Linking.openURL(href)}>
<Text>{children}</Text>
</TouchableWithoutFeedback>
Это позволит нам импортировать модули, специфичные для платформы, которые сломались бы на одной из платформ.
Разделение кода — хороший вариант для создания примитивных компонентов, которые впоследствии можно использовать для написания кроссплатформенной разметки:
<Stack gap="medium">
<Heading>Do or do not</Heading>
<Paragraph>There is no try</Paragraph>
<Link href="/try">Try nonetheless</Link>
</Stack>
Заключение
Написание кроссплатформенных компонентов для web и React Native не так гладко, как обещано, но, выбрав правильные абстракции, мы можем сделать это менее болезненным, а также улучшить читабельность и сопровождаемость кода.
Мой главный совет по созданию кроссплатформенных интерфейсов таков:
Пишите специфичный для платформы код на как можно более низком уровне.
Улучшайте свои примитивные компоненты, чтобы вам не приходилось писать пользовательские стили и разделять код слишком часто.
И дайте мне знать, если у вас есть идеи получше!
Подпишитесь на мою рассылку, если вам понравилась эта статья.