Bridge 00C — React Native Mental Model

Bridge ⏱ 3-5 giờ 📋 Prerequisites: Bridge 00A-00B hoặc biết React cơ bản

Vai trò của trang này

Trang này nối giữa “biết React” và “làm được React Native”. Nó giải thích các khác biệt quan trọng nhất để người mới không áp dụng thói quen web sai vào mobile.

🎯 Mục tiêu bài học

🧠 Kiến thức cốt lõi

1. React Native không phải webview

React Native dùng React để mô tả UI, nhưng output là native views. View tương đương container native, Text tương đương text native, Image tương đương image native. Vì vậy không có DOM, không có CSS cascade, không có thẻ div.

Web ReactReact NativeGhi nhớ
divViewContainer layout.
span/pTextText phải nằm trong Text.
buttonPressableTự style state pressed/disabled.
imgImageCần width/height hoặc style rõ.
inputTextInputKeyboard, focus, secure input là mobile behavior.

2. Layout: Flexbox nhưng default khác web

Trong RN, flexDirection mặc định là column. Kích thước thường phải rõ hơn web, đặc biệt với image, list item, touch target.

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    backgroundColor: '#fff',
  },
  header: {
    height: 56,
    justifyContent: 'center',
    paddingHorizontal: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  content: {
    flex: 1,
    padding: 16,
  },
});

3. ScrollView và FlatList không thay thế nhau

ComponentDùng khiRủi ro
ScrollView Nội dung ít, form, static content. Render toàn bộ children, list dài sẽ nặng.
FlatList Danh sách động/dài. Cần key, item height/layout, memoization khi list phức tạp.

4. Thread model ở mức cần nhớ

Khi JS thread bị block, app có thể lag dù native UI vẫn tồn tại. Đây là nền để học Module 08 Performance và Module 10 Architecture.

💻 Ví dụ code / ví dụ thực tế

Screen cơ bản: profile layout mobile

import { Image, Pressable, SafeAreaView, StyleSheet, Text, View } from 'react-native';

export function ProfileSummaryScreen() {
  return (
    <SafeAreaView style={styles.screen}>
      <View style={styles.header}>
        <Image
          source={{ uri: 'https://placehold.co/96x96/png' }}
          style={styles.avatar}
        />
        <View style={styles.info}>
          <Text style={styles.name}>Mobile Developer</Text>
          <Text style={styles.subtitle}>React Native • Kotlin</Text>
        </View>
      </View>

      <View style={styles.statsRow}>
        <StatBox label="Apps" value="4" />
        <StatBox label="YOE" value="3" />
        <StatBox label="Modules" value="18" />
      </View>

      <Pressable style={styles.primaryButton} onPress={() => {}}>
        <Text style={styles.primaryButtonText}>Open portfolio</Text>
      </Pressable>
    </SafeAreaView>
  );
}

function StatBox({ label, value }: { label: string; value: string }) {
  return (
    <View style={styles.statBox}>
      <Text style={styles.statValue}>{value}</Text>
      <Text style={styles.statLabel}>{label}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, backgroundColor: '#f8fafc' },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 12,
    padding: 16,
    borderRadius: 12,
    backgroundColor: '#fff',
  },
  avatar: { width: 72, height: 72, borderRadius: 36 },
  info: { flex: 1 },
  name: { fontSize: 20, fontWeight: '700', color: '#0f172a' },
  subtitle: { marginTop: 4, color: '#64748b' },
  statsRow: { flexDirection: 'row', gap: 10, marginTop: 12 },
  statBox: {
    flex: 1,
    alignItems: 'center',
    padding: 14,
    borderRadius: 12,
    backgroundColor: '#fff',
  },
  statValue: { fontSize: 20, fontWeight: '700', color: '#2563eb' },
  statLabel: { marginTop: 4, color: '#64748b' },
  primaryButton: {
    minHeight: 48,
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: 16,
    borderRadius: 10,
    backgroundColor: '#2563eb',
  },
  primaryButtonText: { color: '#fff', fontWeight: '700' },
});

🐞 Lỗi thường gặp

LỗiVì sao xảy ra?Fix
Text nằm trực tiếp trong View RN yêu cầu string phải nằm trong Text. Bọc bằng <Text>.
Image không hiện Thiếu width/height hoặc URI không hợp lệ. Đặt style kích thước rõ, kiểm tra network/image source.
Button khó bấm Touch target quá nhỏ. Dùng min height khoảng 44-48, padding đủ, state disabled rõ.
List dài bị lag Dùng ScrollView render tất cả item. Dùng FlatList, key ổn định, tối ưu render item.
Layout vỡ giữa Android/iOS Safe area, font rendering, keyboard, status bar khác nhau. Test cả hai platform, dùng safe area và keyboard handling đúng.

🏢 Case đi làm thật

Case: Màn profile nhìn ổn trên iPhone nhưng vỡ trên Android nhỏ

Nguyên nhân thường là hard-code width/height, text không cho wrap, touch target quá sát, hoặc không tính safe area/status bar. Cách fix không phải scale toàn bộ theo màn hình, mà là dùng flex, min/max constraints, test device nhỏ, và xác định phần nào scroll được.

🧪 Mini exercise thực tế

  1. Tạo ProfileSummaryScreen như ví dụ.
  2. Đổi layout stats từ row sang column trên màn hình nhỏ bằng điều kiện đơn giản theo width.
  3. Thêm một đoạn text dài và đảm bảo không tràn khỏi card.
  4. Thay ScrollView bằng FlatList cho danh sách 100 item.
  5. Ghi lại 3 khác biệt giữa RN styling và CSS web.

⚡ Ghi nhớ nhanh

✅ Câu hỏi tự kiểm tra

  1. React Native khác webview như thế nào?
  2. Vì sao text phải nằm trong Text?
  3. Khi nào dùng FlatList thay vì ScrollView?
  4. Vì sao Image cần kích thước rõ?
  5. JS thread bị block ảnh hưởng app như thế nào?

🎤 Câu hỏi phỏng vấn

  1. Does React Native render HTML? Explain the rendering model.
  2. What are the main differences between styling in React Native and CSS on the web?
  3. When would you use FlatList instead of ScrollView?
  4. What are common platform differences you test for in React Native?
  5. How can JS thread work affect perceived app performance?