Від React до React Native: ключові відмінності
Від React до React Native: ключові відмінності
Те, що ви вже знаєте — і те, що зміниться
Якщо ви пишете на React, ваші знання не зникають — вони переносяться. Компонентна модель, хуки, JSX, однонаправлений потік даних, TypeScript — все це залишається незмінним. Але React Native — це інша платформа, і вона диктує інші правила для UI-рівня.
Уявіть аналогію: ви програміст, який знає алгоритми і структури даних. Ці знання універсальні — вони однаково застосовуються у Python, Go чи Rust. Але синтаксис, стандартна бібліотека, інструменти — різні. Так само і тут: React — це «алгоритми», а React Native — нова «мова» для мобільної платформи.
У цій статті ми систематично пройдемо всі ключові відмінності — від концептуальних до практичних. До кінця у вас буде чіткий «словник перекладу» з React у React Native.
Відмінність 1: DOM не існує
Що таке DOM у веб-React
У браузері React рендерить компоненти у Document Object Model (DOM) — деревоподібну структуру HTML-елементів, якою керує браузер. Коли ви пишете <div className="container">, React створює вузол DOM-дерева, браузер малює його на екрані відповідно до CSS-правил.
// Веб React: JSX → Virtual DOM → реальний DOM
function App() {
return (
<div className="container">
<h1>Заголовок</h1>
<p>Параграф тексту</p>
<button onClick={() => alert('Привіт!')}>Натисни</button>
</div>
)
}
Браузер розуміє <div>, <h1>, <p>, <button> — це стандарт HTML, визначений специфікацією W3C.
Що відбувається у React Native
React Native не має браузера. Він виконується безпосередньо на iOS або Android. Тому він не знає, що таке <div> або <p>. Замість DOM він використовує нативні компоненти — JavaScript-обгортки над платформо-специфічними елементами UI:
- На iOS
<View>стаєUIView(UIKit) - На Android
<View>стаєandroid.view.View - На iOS
<Text>стаєUILabel - На Android
<Text>стаєTextView
// React Native: JSX → Shadow Tree → нативні UIView/View
import { View, Text, Pressable } from 'react-native'
function App() {
return (
<View style={styles.container}>
<Text style={styles.heading}>Заголовок</Text>
<Text>Параграф тексту</Text>
<Pressable onPress={() => alert('Привіт!')}>
<Text>Натисни</Text>
</Pressable>
</View>
)
}
<div>, <span>, <p>, <input>, <button> — всі вони викличуть помилку. Завжди імпортуйте компоненти з 'react-native' або з Expo/community-бібліотек.Повна таблиця відповідностей: HTML → React Native
| HTML (веб) | React Native | Примітки |
|---|---|---|
<div> | <View> | Основний контейнер для layout |
<span> | <Text> | Але Text — блоковий, не рядковий |
<p> | <Text> | Немає семантичного аналога |
<h1>...<h6> | <Text style={...}> | Розмір через style, не тег |
<button> | <Pressable> або <TouchableOpacity> | Нема нативної кнопки-контейнера |
<input type="text"> | <TextInput> | |
<input type="checkbox"> | <Switch> або community Checkbox | |
<img> | <Image> | Обов'язкові width/height для мережевих |
<ul> / <li> | <FlatList> або <View> + map | FlatList для довгих списків |
<select> | <Picker> (community) | Окремий пакет |
<a> | <Pressable> + Linking | Немає href |
<form> | <View> + стан | Немає нативних форм |
<iframe> | <WebView> (community) | Окремий пакет |
<video> | <Video> (expo-av) | |
<canvas> | <Canvas> (react-native-skia) | |
<svg> | react-native-svg | |
<table> | <FlatList> / кастомна таблиця | Немає нативного аналога |
<h1>, <nav>, <article> несуть семантику для пошукових систем та screen readers. У мобільних застосунках доступність реалізована через accessibility props (accessibilityRole, accessibilityLabel), а не через теги. Тому React Native обходиться мінімальним набором компонентів.Відмінність 2: CSS не існує — є StyleSheet
Що зникає разом з DOM
У браузері стилі — це CSS: каскад, специфічність, псевдокласи, медіазапити, анімації через @keyframes, Grid, Float, селектори. React Native не має жодного з цього. Причина проста: немає браузерного движка, немає CSS-парсера.
Натомість React Native використовує StyleSheet — JavaScript-об'єкт зі стилями, який за синтаксисом нагадує CSS, але є лише його невеликою підмножиною.
import { View, Text, StyleSheet } from 'react-native'
function Card() {
return (
<View style={styles.card}>
<Text style={styles.title}>Заголовок</Text>
<Text style={styles.body}>Текст картки</Text>
</View>
)
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
// Тінь на iOS:
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
// Тінь на Android (elevation):
elevation: 3,
},
title: {
fontSize: 18,
fontWeight: '700',
color: '#1a1a1a',
marginBottom: 8,
},
body: {
fontSize: 14,
color: '#666666',
lineHeight: 20,
},
})
Що є у StyleSheet, чого немає в CSS, і навпаки
CSS-властивості, які ДОСТУПНІ у React Native (з відмінностями):
| CSS | React Native StyleSheet | Відмінність |
|---|---|---|
background-color: red | backgroundColor: 'red' | camelCase, рядкове значення |
font-size: 16px | fontSize: 16 | Без одиниць, числове значення |
margin: 10px 20px | marginVertical: 10, marginHorizontal: 20 | Немає shorthand-синтаксису |
border: 1px solid #ccc | borderWidth: 1, borderColor: '#ccc' | Немає shorthand |
border-radius: 8px | borderRadius: 8 | |
display: flex | За замовчуванням (Flexbox увімкнений завжди) | Немає display: block/inline/grid |
flex-direction: row | flexDirection: 'row' | За замовч. 'column', а не 'row'! |
opacity: 0.5 | opacity: 0.5 | Однаково |
overflow: hidden | overflow: 'hidden' | |
position: absolute | position: 'absolute' | Немає fixed, sticky |
z-index: 10 | zIndex: 10 |
CSS-можливості, яких у React Native НЕМАЄ зовсім:
| CSS-концепція | Причина відсутності | Альтернатива у RN |
|---|---|---|
::before, ::after | Немає CSS-парсера | Додатковий <View> |
:hover, :focus | Немає миші | onPressIn, onFocus props |
@media (медіазапити) | useWindowDimensions hook | |
@keyframes | Animated API або Reanimated | |
display: grid | Flexbox з numColumns у FlatList | |
float: left | Flexbox flexDirection: 'row' | |
position: fixed | Абсолютне позиціонування + SafeAreaView | |
position: sticky | stickyHeaderIndices у ScrollView | |
| CSS Grid | Flexbox | |
| CSS Variables | JS constants або Theme Context | |
| CSS-селектори | Props та стиль на кожен компонент | |
| Каскад (cascade) | Явна передача стилів через props | |
| Класи (className) | style prop |
style prop.Чому StyleSheet.create(), а не звичайний об'єкт?
Технічно можна писати стилі як звичайний JS-об'єкт:
// Це теж працює, але не рекомендується:
<View style={{ backgroundColor: 'red', padding: 16 }}>
Але StyleSheet.create() має переваги:
- Перевірка типів — TypeScript підкаже, якщо ви напишете неправильну властивість
- Оптимізація — у New Architecture стилі реєструються один раз і передаються за ID, а не копіюються щоразу
- Читабельність — стилі відокремлені від розмітки, як у CSS-in-JS бібліотеках
Відмінність 3: Обробка подій
onClick → onPress (і набагато більше)
У вебі майже всі інтерактивні елементи отримують події через onClick. На мобільному пристрої немає миші — є дотик (touch). Це породжує іншу модель подій:
onClick → onPress
Основний обробник дотику. Спрацьовує після завершення натискання (аналог mouseup). Доступний на Pressable, TouchableOpacity, Button.
<Pressable onPress={() => console.log('натиснуто')}>
<Text>Кнопка</Text>
</Pressable>
onPressIn / onPressOut
Спрацьовують на початку та кінці дотику (аналоги mousedown/mouseup). Використовуються для анімацій натискання — змінити стиль «під час натискання».
<Pressable
onPressIn={() => setScale(0.95)}
onPressOut={() => setScale(1)}
>
onLongPress
Спрацьовує після утримання дотику (за замовч. 500 мс). Типовий мобільний патерн: «довге натискання відкриває контекстне меню».
<Pressable onLongPress={() => showContextMenu()}>
onChangeText (TextInput)
Аналог onChange для текстових полів. Але отримує вже готовий рядок string, а не event об'єкт. Не потрібно event.target.value.
<TextInput onChangeText={(text) => setValue(text)} />
Повна таблиця відповідностей подій
| Веб-подія | React Native | Компонент |
|---|---|---|
onClick | onPress | Pressable, TouchableOpacity |
onMouseDown | onPressIn | Pressable |
onMouseUp | onPressOut | Pressable |
onDoubleClick | (немає стандартного) | Gesture Handler: Tap({ count: 2 }) |
onChange (input) | onChangeText | TextInput |
onFocus | onFocus | TextInput |
onBlur | onBlur | TextInput |
onScroll | onScroll | ScrollView, FlatList |
onSubmit (form) | onSubmitEditing | TextInput |
onKeyDown | (обмежено) | TextInput: onKeyPress |
| Жести миші | Gestures | react-native-gesture-handler |
Відмінність 4: Браузерні API зникають
React Native виконується поза браузером, тому всі window.*, document.* та подібні API недоступні. Ось повна таблиця замін:
| Браузерний API | React Native аналог | Пакет |
|---|---|---|
localStorage | AsyncStorage | @react-native-async-storage/async-storage |
sessionStorage | AsyncStorage (або пам'ять) | — |
fetch() | fetch() | вбудований у RN |
navigator.geolocation | expo-location | expo-location |
Notification API | expo-notifications | expo-notifications |
navigator.mediaDevices.getUserMedia | expo-camera | expo-camera |
window.open(url) | Linking.openURL(url) | вбудований Linking |
window.alert() | Alert.alert() | вбудований Alert |
window.innerWidth/Height | useWindowDimensions() | вбудований хук |
document.title | <Stack.Screen options={{ title }}> | expo-router |
history.pushState() | router.push() | expo-router |
WebSocket | WebSocket | вбудований у RN |
Canvas API | react-native-skia | @shopify/react-native-skia |
clipboard API | expo-clipboard | expo-clipboard |
fetch — виняток:fetch() є у React Native «з коробки» і поводиться так само, як у браузері. Це один з небагатьох браузерних API, що доступний напряму. XMLHttpRequest також підтримується (але рідко використовується — зазвичай через Axios або RTK Query).Відмінність 5: Платформо-залежна поведінка iOS vs Android
На відміну від веб-браузерів, де є стандарт (W3C), iOS і Android — це дві різні платформи з різними конвенціями, шрифтами і поведінкою UI. React Native намагається надати єдиний API, але деякі речі різняться:
const styles = StyleSheet.create({
box: {
// iOS — через shadow-властивості:
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 6,
// Android — через elevation:
elevation: 5,
// Обидва на одному компоненті — кожна
// платформа використовує своє:
backgroundColor: '#fff', // обов'язково для iOS shadow
},
})
const styles = StyleSheet.create({
text: {
// iOS: системний шрифт — San Francisco
// Android: системний шрифт — Roboto
// fontFamily: undefined → кожна платформа
// використовує свій системний шрифт
// Кастомний шрифт однаковий на обох платформах:
fontFamily: 'Inter_400Regular',
},
boldText: {
// На iOS fontWeight: 'bold' = SF Pro Bold
// На Android fontWeight: 'bold' = Roboto Bold
fontWeight: 'bold',
},
})
import { KeyboardAvoidingView, Platform } from 'react-native'
// Поведінка при появі клавіатури різна:
;<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>{/* форма */}</KeyboardAvoidingView>
// iOS: клавіатура перекриває контент знизу,
// KeyboardAvoidingView з 'padding' додає відступ
// Android: система автоматично коригує layout
// (windowSoftInputMode: adjustResize)
// iOS: Stack-навігація зі swipe-back (свайп вліво = назад)
// Android: кнопка «Назад» (hardware або в navigation bar)
// Обробка кнопки "Назад" тільки на Android:
import { BackHandler } from 'react-native'
import { useEffect } from 'react'
useEffect(() => {
const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
// true — ми обробили подію, система не реагує
// false — система обробляє стандартно
return true
})
return () => subscription.remove()
}, [])
Перший живий приклад: React Native у браузері
Завдяки компоненту ::react-native-preview ви можете побачити React Native компоненти прямо у браузері — вони рендеряться через react-native-web. Це не повноцінний емулятор, але достатньо для розуміння базових концепцій UI.
Loading…
react-native-web · not a real device
import { View, Text, Pressable, StyleSheet } from 'react-native'
import { useState } from 'react'
export default function FirstExample() {
const [count, setCount] = useState(0)
const [pressed, setPressed] = useState(false)
return (
<View style={styles.container}>
<Text style={styles.title}>Мій перший RN-компонент</Text>
<Text style={styles.subtitle}>Це нативні компоненти, а не HTML</Text>
<View style={styles.counterBox}>
<Text style={styles.counterLabel}>Лічильник</Text>
<Text style={styles.counterValue}>{count}</Text>
</View>
<Pressable
style={[styles.button, pressed && styles.buttonPressed]}
onPress={() => setCount((c) => c + 1)}
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
>
<Text style={styles.buttonText}>Натисни мене</Text>
</Pressable>
<Pressable style={styles.resetButton} onPress={() => setCount(0)}>
<Text style={styles.resetText}>Скинути</Text>
</Pressable>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 24,
backgroundColor: '#f8f9fa',
},
title: {
fontSize: 22,
fontWeight: '700',
color: '#1a1a2e',
marginBottom: 8,
textAlign: 'center',
},
subtitle: {
fontSize: 13,
color: '#6c757d',
marginBottom: 32,
textAlign: 'center',
},
counterBox: {
backgroundColor: '#ffffff',
borderRadius: 16,
padding: 24,
alignItems: 'center',
marginBottom: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
elevation: 3,
width: '100%',
},
counterLabel: {
fontSize: 12,
color: '#6c757d',
textTransform: 'uppercase',
letterSpacing: 1,
marginBottom: 8,
},
counterValue: {
fontSize: 64,
fontWeight: '800',
color: '#4361ee',
},
button: {
backgroundColor: '#4361ee',
paddingVertical: 16,
paddingHorizontal: 40,
borderRadius: 12,
marginBottom: 12,
width: '100%',
alignItems: 'center',
},
buttonPressed: {
backgroundColor: '#3451d1',
opacity: 0.9,
},
buttonText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
},
resetButton: {
paddingVertical: 12,
},
resetText: {
color: '#6c757d',
fontSize: 14,
},
})
Зверніть на декілька речей у цьому прикладі:
- Немає
<div>чи<button>— лише<View>,<Text>,<Pressable>зreact-native - Стан (
useState) — ідентичний веб-React, жодних відмінностей StyleSheet.create()— всі стилі зосереджені внизу, stilo CSS-in-JSpressedстан —onPressIn/onPressOutдозволяють змінювати стиль «під час натискання» — це мобільний аналог CSS:active[styles.button, pressed && styles.buttonPressed]— масив стилів, де другий перекриває перший
Практика
Ось веб React компонент. Перепишіть його для React Native:
function ProfileCard({ name, role, avatar }: { name: string; role: string; avatar: string }) {
const [liked, setLiked] = useState(false)
return (
<div className="card" style={{ padding: 20, borderRadius: 12 }}>
<img src={avatar} alt={name} width={64} height={64} />
<h2>{name}</h2>
<p style={{ color: 'gray' }}>{role}</p>
<button onClick={() => setLiked((l) => !l)}>{liked ? '❤️ Вподобано' : '🤍 Вподобати'}</button>
</div>
)
}
Підказки:
<div>→<View><img>→<Image>(потрібніwidth/heightуstyle)<h2>→<Text style={{ fontSize: 20, fontWeight: '700' }}><p>→<Text><button onClick>→<Pressable onPress>className→style={StyleSheet.create(...)}
Розгляньте наступний код. Знайдіть 5 помилок, характерних для новачків, що переходять з React-веб на React Native:
import { View } from 'react-native'
function BuggyComponent() {
return (
<div style={{ display: 'flex', flexDirection: 'row' }}>
<span onClick={() => console.log('click')}>Натисни мене</span>
<p className="text" style={{ color: 'blue' }}>
Параграф
</p>
<img src="https://example.com/photo.jpg" />
</div>
)
}
Відповідь:
<div>→ має бути<View><span>→ має бути<Text>всередині<Pressable>onClick→ має бутиonPressна<Pressable><p>→ має бути<Text>className→ немає в React Native;<img>→<Image>з обов'язковимиwidth/height
У CSS каскад дозволяє стилям «просочуватися» від батьківського елемента до дочірніх. Наприклад, якщо встановити color: red на <div>, всі вкладені <span> також стануть червоними.
У React Native цього немає. Подумайте: чому? Які переваги та недоліки має такий підхід? Як це впливає на компонентну архітектуру?
Як влаштований React Native «під капотом»
Просте пояснення середовища виконання React Native — від коду на TypeScript до кнопок на екрані телефону, без зайвих абревіатур на старті
View та Text — фундаментальні будівельні блоки
View як контейнер для всього, Text як єдиний спосіб відображення тексту — глибоке розуміння двох найважливіших компонентів React Native, їх відмінностей від HTML та правил використання.