npx create-expo-app@latest rn-playground hoặc CLI: npx react-native init RNPlaygroundText.flexDirection: 'column', mobile ưu tiên layout dọc.| Khía cạnh | React Web | React Native |
|---|---|---|
| Render target | DOM (div, span, p) | Native Views (UIView, android.view.View) |
| Styling | CSS files, cascade, inheritance | JS objects (StyleSheet), no cascade |
| Layout | CSS (box model, grid, flexbox) | Yoga engine (flexbox only, default column) |
| Units | px, em, rem, %, vh/vw | dp (density-independent pixels), % |
| Animation | CSS transitions, Web Animations API | Animated API, Reanimated |
| Navigation | React Router (URL-based) | React Navigation (stack-based) |
Key insight: RN không dùng WebView. JSX compile thành native platform components thông qua Bridge/JSI.
| Component | Vai trò | Khi nào dùng |
|---|---|---|
View | Container (như div) | Mọi nơi cần wrapper, layout |
SafeAreaView | Tránh notch/status bar | Root container trên iOS |
KeyboardAvoidingView | Đẩy content lên khi keyboard hiện | Screens có form input |
ScrollView | Scrollable container | Content ngắn (<50 items), forms |
| Component | Vai trò | Lưu ý |
|---|---|---|
Text | Hiển thị text | BẮT BUỘC wrap text trong Text (khác web) |
Image | Hiển thị ảnh | require() cho local, {uri} cho remote |
StatusBar | Control status bar style | barStyle, backgroundColor (Android) |
Modal | Overlay screen | animationType, transparent |
| Component | Vai trò | Ưu tiên dùng |
|---|---|---|
Pressable | Universal pressable wrapper | ⭐ Recommended (mới nhất) |
TouchableOpacity | Press với opacity feedback | Vẫn phổ biến, quen thuộc |
TextInput | Input field | Controlled component |
| ScrollView | FlatList | SectionList | |
|---|---|---|---|
| Render | Tất cả cùng lúc | Virtualized (chỉ render visible) | Virtualized + sections |
| Performance | Kém với nhiều items | Tốt (recycle views) | Tốt |
| Khi nào | <50 items, forms | Long lists, homogeneous | Grouped data (contacts) |
StyleSheet.create() — tạo style objects. Tại sao không dùng inline objects?
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0f1117',
paddingHorizontal: 16,
},
title: {
fontSize: 24,
fontWeight: '700',
color: '#e4e6f0',
marginBottom: 8,
},
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
});
Không có CSS cascade: Mỗi component phải khai báo style riêng. Text bên trong View không kế thừa color từ View (trừ Text nested trong Text).
RN default: flexDirection: 'column' (khác web mặc định row)
| Property | Giá trị phổ biến | Mẹo nhớ |
|---|---|---|
flexDirection | column | row | column = dọc (default), row = ngang |
justifyContent | center | space-between | flex-start | Main axis (theo flexDirection) |
alignItems | center | flex-start | stretch | Cross axis (vuông góc) |
flex: 1 | number | Chiếm hết không gian còn lại |
flexWrap | wrap | nowrap | Có xuống dòng không |
gap | number | Khoảng cách giữa children (RN 0.71+) |
position | relative | absolute | absolute: thoát khỏi flex flow |
Mẹo: Khi layout bị sai → check flexDirection trước. Nhớ default là column!
// Local (bundled) - require
<Image source={require('./assets/logo.png')} />
// Remote - uri object
<Image
source={{ uri: 'https://example.com/photo.jpg' }}
style={{ width: 200, height: 200 }}
/>
Lưu ý quan trọng: Remote images BẮT BUỘC phải set width + height (không tự động như web). Local images RN biết dimensions từ metadata.
| resizeMode | Mô tả |
|---|---|
| cover | Scale để fill, crop nếu cần (phổ biến nhất) |
| contain | Scale vừa khung, không crop, có thể có space |
| stretch | Kéo giãn vừa khung, có thể méo |
| center | Không scale, center align |
const [email, setEmail] = useState('');
<TextInput
value={email}
onChangeText={setEmail}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
returnKeyType="next"
style={styles.input}
/>
Keyboard types: default, email-address, numeric, phone-pad, decimal-pad
import { useWindowDimensions, PixelRatio } from 'react-native';
const MyComponent = () => {
const { width, height } = useWindowDimensions();
const isTablet = width >= 768;
return (
<View style={{ padding: isTablet ? 32 : 16 }}>
{/* responsive content */}
</View>
);
};
useWindowDimensions vs Dimensions.get(): useWindowDimensions tự update khi xoay màn hình (reactive). Dimensions.get() là static — cần event listener.
import { Platform } from 'react-native';
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
},
android: {
elevation: 5,
},
}),
});
File extensions: Component.ios.tsx / Component.android.tsx — RN tự chọn file đúng platform.
| Platform | behavior prop |
|---|---|
| iOS | "padding" (recommended) |
| Android | "height" hoặc không cần (android:windowSoftInputMode="adjustResize") |
Tip: Dùng react-native-keyboard-aware-scroll-view cho forms phức tạp — tự scroll tới focused input.
import { KeyboardAvoidingView, Platform, ScrollView, TextInput } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
function RegisterScreen() {
return (
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<ScrollView
keyboardShouldPersistTaps="handled"
contentContainerStyle={{ padding: 16, gap: 12 }}
>
<TextInput placeholder="Email" keyboardType="email-address" />
<TextInput placeholder="Password" secureTextEntry />
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
Native views. RN map JSX components sang platform-native views: View → UIView (iOS) / android.view.View (Android). Không dùng WebView — đây là điểm khác biệt chính so với Cordova/Ionic. Kết quả: performance gần native, nhưng phải bridge giữa JS thread và native thread.
RN không có DOM text nodes. Mỗi text phải là Text component → được render thành UILabel (iOS) / TextView (Android). Viết text trực tiếp trong View sẽ crash vì View không biết render text.
ScrollView: render TẤT CẢ children cùng lúc. Dùng cho: forms, content ngắn (<50 items), mixed content.
FlatList: virtualized — chỉ render items trong viewport + buffer. Dùng cho: long lists, homogeneous data, dynamic content.
Rule: Nếu list có thể >50 items hoặc dynamic → FlatList. Nếu static content → ScrollView.
getItemLayout — skip layout measurement (nếu items cùng height)keyExtractor — unique stable key (không dùng index)removeClippedSubviews={true} — unmount off-screen items (Android)maxToRenderPerBatch — giảm items render mỗi batchwindowSize — giảm viewport bufferReact.memo cho renderItem component@shopify/flash-list thay thế — nhanh hơn đáng kể1) Validation tại build time. 2) Performance: style ID gửi qua Bridge 1 lần. 3) Code organization. Tuy nhiên với New Architecture (JSI), performance gap nhỏ hơn vì không còn serialization overhead.
Default: column (web default: row). Lý do: mobile screens dọc (portrait) → content tự nhiên flow từ trên xuống. Design decision của Facebook khi tạo Yoga layout engine.
iOS: KeyboardAvoidingView với behavior="padding".
Android: Set android:windowSoftInputMode="adjustResize" trong AndroidManifest.xml — system tự handle. Hoặc dùng behavior="height".
Cả 2: react-native-keyboard-aware-scroll-view — auto scroll tới focused input.
Platform.OS === 'ios' — simple conditionalPlatform.select({ ios: ..., android: ... }) — object mapping.ios.tsx / .android.tsx — khi logic khác nhau nhiềuPlatform.Version — check OS version numberView làm app crash.windowSoftInputMode.elevation.Case: Form đăng ký nhìn ổn trên iPhone, nhưng trên Android keyboard che nút Submit. User không biết cách hoàn tất form.
Cách xử lý: Dùng ScrollView với keyboardShouldPersistTaps, cấu hình Android adjustResize, test cả màn hình nhỏ và input cuối form.
Nền tảng RN tốt là biết các khác biệt nhỏ giữa web, iOS và Android. Mọi màn hình cơ bản nên được test với keyboard, safe area, màn hình nhỏ, ảnh remote và list đủ dài.