React Native aplikacija koja se ponaša identično na iOS-u i Androidu rijetko zadovoljava korisnike — te dvije platforme imaju različite design konvencije, gestove, sustavske boje i API-je. Za rješavanje tih razlika React Native nudi tri pristupa koji se nadopunjuju: Platform.OS i Platform.select() za uvjetnu logiku unutar jedne datoteke, platformski specifične datoteke (.ios.tsx i .android.tsx) za potpuno razdvojene implementacije, te PlatformColor za boje koje automatski prate sustavsku temu.
Odabir pravog pristupa smanjuje dupliranje koda i osigurava da se aplikacija na svakoj platformi ponaša onako kako korisnici očekuju — bez nagomilavanja uvjetne logike po cijeloj bazi koda.
Platform.OS i Platform.select()
Platform je ugrađeni modul React Nativea koji pruža informacije o okolini u kojoj se aplikacija izvodi. Najčešće korišten je Platform.OS — niz znakova čija je vrijednost 'ios', 'android' ili 'web' u projektima s React Native Webom. Izravna usporedba prikladan je za jednokratne uvjete:
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 20 : 0,
},
});
Za odabir između više vrijednosti bolje je koristiti Platform.select() koji prima objekt čiji su ključevi nazivi platformi. Ključ default hvata sve ostale platforme i preporučuje se uvijek kako bi se izbjegla neočekivana vrijednost undefined:
import { Platform } from 'react-native';
const hitSlop = Platform.select({
ios: { top: 10, bottom: 10, left: 10, right: 10 },
android: { top: 8, bottom: 8, left: 8, right: 8 },
default: { top: 10, bottom: 10, left: 10, right: 10 },
});
Platform.select() procjenjuje se pri izvođenju, ne pri bundliranju. To znači da JavaScript bundler ne može ukloniti grane za nekorištene platforme iz produkcijskog paketa. Kad su razlike između platformi veće od nekoliko redaka ili kad se koriste različiti native API-ji, bolje je posegnuti za platformski specifičnim datotekama.
Za provjeru verzije operacijskog sustava dostupan je Platform.Version. Na Androidu vraća broj koji odgovara API razini, a na iOS-u niz znakova:
import { Platform } from 'react-native';
if (
Platform.OS === 'android' &&
typeof Platform.Version === 'number' &&
Platform.Version >= 31
) {
// Material You dinamičke boje dostupne su od Android 12 (API razina 31)
}
Platformski specifične datoteke
Metro bundler podržava konvenciju imenovanja datoteka s platformskim sufiksom. Ako postoje datoteke Button.ios.tsx i Button.android.tsx, uvoz import Button from './Button' automatski razrješuje odgovarajuću datoteku za ciljnu platformu — a u produkcijskom buildu u paket ulazi samo ona koja je potrebna za tu platformu.
Ovaj pristup preporučuje se kad:
- se renderirana JSX struktura komponente bitno razlikuje između platformi,
- se koriste potpuno različiti native API-ji ili animacijske strategije,
- uvjetna logika unutar jedne datoteke čini komponentu teškom za čitanje i testiranje.
Tipična struktura direktorija za komponentu s platformskim varijantama:
components/
HapticButton/
index.ts <-- re-export i zajednički tipovi
HapticButton.ios.tsx <-- iOS implementacija s haptic feedbackom
HapticButton.android.tsx <-- Android implementacija s vibracijom
Datoteka index.ts samo re-exporta komponentu i definira zajednički tip propa, čime se osigurava konzistentan javni API bez obzira na platformu:
// index.ts
export { default } from './HapticButton';
export type { HapticButtonProps } from './HapticButton.ios';
Ekstenzija .native.tsx za React Native Web
U projektima koji istovremeno ciljaju mobilne platforme i web putem React Native Weba, ekstenzija .native.tsx označava kod koji se koristi na svim nativnim platformama (iOS i Android), dok datoteka bez sufiksa služi kao web implementacija. Prioritet razrješavanja je: .ios.tsx i .android.tsx ispred .native.tsx ispred .tsx.
components/
ShareButton/
ShareButton.native.tsx <-- iOS i Android (Share API)
ShareButton.tsx <-- web fallback
PlatformColor i sustavske boje
PlatformColor je React Native API koji boji pridružuje sustavski semantički token umjesto statičke heksadecimalne vrijednosti. Na iOS-u se mapira na UIKit semantičke boje poput systemBackground i label, a na Androidu na resurse boja ili atribute teme poput ?attr/colorSurface. U kombinaciji s Platform.select() može se koristiti u stilovima koji dijele istu datoteku:
import { Platform, PlatformColor, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
screen: {
backgroundColor: Platform.select({
ios: PlatformColor('systemBackground'),
android: PlatformColor('?attr/colorSurface'),
default: '#ffffff',
}),
},
label: {
color: Platform.select({
ios: PlatformColor('label'),
android: PlatformColor('?attr/colorOnSurface'),
default: '#000000',
}),
},
});
Prednost ovog pristupa nad ručnim upravljanjem tamnim načinom rada je automatska prilagodba: kad korisnik promijeni sustavsku temu, iOS i Android ažuriraju boje bez ikakva React koda. Nema potrebe za slušanjem useColorScheme() za svaku boju koja prati sustavski dizajn.
DynamicColorIOS
Na iOS-u postoji i DynamicColorIOS, specifičan za tu platformu, koji prima objekt s ključevima light i dark za eksplicitno definiranje prilagodbe temi aplikacije kad ne postoji odgovarajući semantički token:
import { DynamicColorIOS, Platform } from 'react-native';
const brandPrimary =
Platform.OS === 'ios'
? DynamicColorIOS({ light: '#0A84FF', dark: '#409CFF' })
: '#0A84FF';
DynamicColorIOS je koristan kada se boja aplikacije razlikuje između svjetlog i tamnog načina rada, ali ne postoji odgovarajući iOS semantički token za taj slučaj.
Praktični uzorci
Platformska logika najlakše se održava kad je izolirana u zasebnim modulima umjesto raspršena po cijeloj aplikaciji. Nekoliko uzoraka koji smanjuju kognitivno opterećenje:
Hook za platformske vrijednosti
Kad više komponenti treba isti set platformskih vrijednosti — npr. veličine ikona, animacijske parametre ili rubove koji prate design konvencije — preporučuje se hook koji ih centralizira:
import { Platform } from 'react-native';
import { useMemo } from 'react';
export function usePlatformSpacing() {
return useMemo(() => ({
screenPadding: Platform.select({ ios: 16, android: 16, default: 16 }),
cardRadius: Platform.select({ ios: 12, android: 8, default: 8 }),
iconSize: Platform.select({ ios: 24, android: 22, default: 24 }),
}), []);
}
Platformski uvjetni render u navigaciji
Navigacijske komponente često trebaju platformski specifične opcije. Pri korištenju Expo Routera v6, opcije zaslona mogu se proslijediti uvjetno bez potrebe za posebnom platformskom datotekom:
import { Stack } from 'expo-router';
import { Platform } from 'react-native';
export default function Layout() {
return (
<Stack
screenOptions={{
headerLargeTitle: Platform.OS === 'ios',
animation: Platform.select({
ios: 'default',
android: 'slide_from_right',
default: 'none',
}),
}}
/>
);
}
Izbjegavanje rasipanja grananja po stablu
Kad se razlike između platformi tiču samo vizualnih detalja — margine, sjene, veličine fonta — preporučuje se Platform.select() unutar StyleSheet.create(). Kad su razlike supstancijalnije, platformske datoteke čine grananje eksplicitnim, testabilnim i oslobođenim od mrtvog koda u bundle-u jer Metro uključuje isključivo odgovarajuću datoteku.
Testiranje platformskog koda
Jest i React Native Testing Library dozvoljavaju simulaciju platforme izmjenom vrijednosti Platform.OS unutar testa. Direktno postavljanje vrijednosti je najjednostavniji pristup koji radi u svim podešavanjima:
import { Platform } from 'react-native';
import { renderHook } from '@testing-library/react-native';
import { usePlatformSpacing } from './usePlatformSpacing';
describe('usePlatformSpacing', () => {
it('vraća iOS vrijednosti', () => {
(Platform as any).OS = 'ios';
const { result } = renderHook(() => usePlatformSpacing());
expect(result.current.cardRadius).toBe(12);
});
it('vraća Android vrijednosti', () => {
(Platform as any).OS = 'android';
const { result } = renderHook(() => usePlatformSpacing());
expect(result.current.cardRadius).toBe(8);
});
});
Za end-to-end testove, Maestro i Detox automatski ciljaju pravo izvođenje na odgovarajućoj platformi, pa platformska logika ne zahtijeva poseban tretman na toj razini testiranja — testovi se pokreću zasebno za iOS i Android i svaki dobiva stvarno okruženje.
Česta pitanja
Kad koristiti Platform.select() a kad platformske datoteke?
Platform.select() je prikladan za male razlike — drugačiji padding, veličina ikone, tip animacije. Platformske datoteke (.ios.tsx, .android.tsx) bolji su izbor kad se JSX struktura bitno razlikuje, kad se koriste različiti native API-ji ili kad uvjetna logika unutar jedne datoteke otežava čitanje i testiranje koda.
Utječe li Platform.select() na veličinu produkcijskog paketa?
Da — Platform.select() procjenjuje se pri izvođenju, pa JavaScript bundler ne može ukloniti grane za nekorištene platforme. Platformske datoteke bolje su za eliminaciju mrtvog koda jer Metro u produkcijskom buildu uključuje samo datoteku koja odgovara ciljnoj platformi, a ostatak se ne isporučuje korisniku.
Radi li PlatformColor s bibliotekama za stiliziranje kao što su NativeWind ili Unistyles?
PlatformColor je React Native API koji radi s StyleSheet.create() i inline stilovima. Biblioteke poput NativeWind i Unistyles imaju vlastite mehanizme za sustavske boje i semantičke tokene — provjerite dokumentaciju odgovarajuće biblioteke za preporučeni način integracije s platformskim bojama.
Može li se platformska logika koristiti i unutar native modula?
Da. Pri razvoju native modula s Expo Modules API-jem, Swift kod se kompajlira samo za iOS, a Kotlin samo za Android — nema potrebe za platformskim provjeram unutar native koda. Na JavaScript razini, hook koji eksportira native modul može koristiti platformske datoteke kako bi pozivao odgovarajuću native implementaciju.
Što se dogodi kad platforma nije ni iOS ni Android?
Platform.OS vraća 'web' u React Native Web okruženju. Platform.select() koristi ključ 'web' ili 'default' kao fallback. Za projekte koji ciljaju web, preporučuje se eksplicitno testirati ponašanje na svim platformama i koristiti .native.tsx ekstenziju za dijeljeni mobilni kod.




