โหมดมืด
บทที่ 12 — React Native (Mobile Application)
🔴 ระดับ: สูง — อ่านทีหลังได้ บทนี้ออกนอกโลกเว็บไปทำแอปมือถือ ถ้ายังโฟกัสเว็บอยู่ ข้ามไปก่อนได้
📖 คำศัพท์รวมของบท:
- React Native = เครื่องมือเขียนแอปมือถือ iOS/Android ด้วย React
- native = ของแท้ของ OS —
<Button>ของ iOS/Android โดยตรง (ไม่ใช่ HTML)- native module = ฟีเจอร์เครื่อง (camera, GPS, sensor, push) ที่ JS เรียก native code
- Expo = framework ครอบ React Native ทำให้ใช้ง่าย (ไม่ต้อง Xcode/Android Studio เริ่ม)
- EAS (Expo Application Services) = cloud service ของ Expo สำหรับ build + submit + OTA (มี free tier)
- Expo Go = แอปทดสอบบนมือถือ — scan QR แล้วเปิด project ของเราได้ทันที (ใช้ library ที่ Expo bundle ไว้)
- Dev Build = build แอปของเราเองที่มี native module พิเศษ (ใช้ Firebase, MMKV ฯลฯ) — ใช้แทน Expo Go เมื่อจำเป็น
- OTA (Over-The-Air) Update = update JS ผ่านอินเตอร์เน็ตโดยที่ user ไม่ต้องไป store download ใหม่
- safe area = พื้นที่ปลอดภัยที่ notch/status bar/home indicator ไม่บัง
- bridge = สะพานเก่าที่ส่งคำสั่ง JS ↔ native (ผ่าน JSON)
- JSI (JavaScript Interface) = สะพานแบบใหม่ที่เร็วกว่า — JS เรียก native ตรงๆ
- New Architecture = ชุด feature ใหม่ที่ใช้ JSI (Fabric + TurboModules) → เร็วกว่า bridge เดิม — default ตั้งแต่ RN 0.76 (ปี 2024)
💡 dev มือใหม่: Expo จัดการเรื่อง bridge/JSI/New Architecture ให้แล้ว — ไม่ต้องเข้าใจ internals (เบื้องลึก — กลไกภายในของ library/framework) พวกนี้ก็เขียน app ได้ ผ่านบทนี้แล้วค่อยขุดลึกเมื่อจำเป็น
ถ้ารู้ React แล้ว — React Native ทำให้คุณสร้าง app iOS + Android ด้วย JS
บทนี้สอน:
- React Native ทำงานยังไง (เทียบกับ web)
- Expo — framework ปี 2026 ที่ทำให้ React Native ใช้ง่าย
- Component ที่ต่างจาก web
- Navigation (React Navigation / Expo Router)
- Style + Layout
- Native module
- Deploy ขึ้น store
บทนี้ภาพรวม — เรียนเชิงลึกควรอ่าน docs Expo + React Native
1. React Native คืออะไร
React Native = "เขียน React แต่ render เป็น native UI ของ mobile (UIView ของ iOS, View ของ Android)"
ลองนึกถึงแอป Grab กับเว็บ Grab บนมือถือ — เว็บ Grab คือเบราว์เซอร์ขนาดเล็กที่ฝังอยู่ใน app (WebView) ปุ่มและตัวอักษรเป็นของเบราว์เซอร์ ไม่ใช่ของ iOS หรือ Android โดยตรง ส่วนแอป Grab จริงๆ ใช้ปุ่มของ iOS/Android โดยตรง — กดแล้วรู้สึกต่างกัน มีแรงสั่นสะเทือน (haptic) ตอบสนองแบบที่ระบบออกแบบมา ผิวสัมผัสเป็นของ native จริงๆ นั่นแหละคือ native UI React Native สร้าง UI แบบหลัง — ไม่ใช่เว็บที่ฝังในแอป แต่เป็น UI ของระบบจริงๆ (เบื้องหลังคือ UIView ของ iOS / View ของ Android — คลาสพื้นฐานสุดที่ native app ทุกตัวถูกสร้างจาก ไม่ต้องจำหรือแตะตรงนี้เอง แค่รู้ไว้ว่ามันคือรากฐานที่ React Native ไปเกาะอยู่)
text
React Web React Native
───────── ────────────
<div> ↔ <View>
<span> ↔ <Text>
<img> ↔ <Image>
<button> ↔ <Pressable>
<input> ↔ <TextInput>
CSS ↔ StyleSheet (object)ไม่ใช่ "หุ้ม WebView (component ที่ฝัง browser เล็ก ๆ ใน app — เหมือนเปิดเว็บอยู่ในแอป)" — render เป็น native จริง ๆ → performance ดี + look-and-feel native (หน้าตาและการใช้งานเหมือน app ของระบบเอง)
2. React Native vs Flutter vs Native
| React Native | Flutter | Native (Swift/Kotlin) | |
|---|---|---|---|
| Language | JavaScript/TS | Dart | Swift / Kotlin |
| Code reuse | iOS + Android + Web (~80%) | iOS + Android + Web | แต่ละ platform แยก |
| Performance | ดี | ดีกว่า | ดีที่สุด |
| วิธีคุยกับ OS (JS ↔ native) | ผ่านตัวกลาง (ช้ากว่านิดหน่อย) | วาด UI เอง (ไม่ต้องคุยกับ OS) | ตรง (เร็วที่สุด) |
| Learning curve (สำหรับ React dev) | ⭐ ต่ำมาก | สูง (ภาษาใหม่) | สูงมาก |
| Hot reload | ✅ | ✅ | ❌ |
| Community | ใหญ่ | กลาง-ใหญ่ | ใหญ่ |
ถ้าคุณรู้ React อยู่แล้ว → React Native = ทางเลือกที่ไว้ใจได้
Part 1: Setup with Expo
3. ทำไมใช้ Expo
Expo = framework + tooling รอบ React Native:
- ไม่ต้องตั้ง Xcode / Android Studio ตั้งแต่ต้น
- มี "Expo Go" app — scan QR → run app บนมือถือทันที
- มี module สำเร็จ (camera, location, sensors, ...)
- ปี 2026 = standard ใช้ React Native (ตรวจ SDK ล่าสุดได้ที่ expo.dev)
⭐ React Native New Architecture (default ตั้งแต่ RN 0.76)
💡 dev มือใหม่: Expo จัดการเรื่อง New Architecture ให้แล้วทั้งหมด — ไม่ต้องเข้าใจรายละเอียดข้างล่างก็เขียน app ได้ปกติ อ่านผ่านตาไว้รู้จักชื่อก็พอ
ปี 2026 React Native ใช้ New Architecture เป็น default วิธีเข้าใจง่ายที่สุดคือเปรียบวิธีที่ JS คุยกับระบบ native ของเครื่อง แบบเก่าเหมือน ฝากคนกลางแปลจดหมาย — JS เขียนคำสั่งเป็น JSON ส่งให้ bridge แปลงแล้วส่งต่อให้ native ซึ่งใช้เวลา แบบใหม่เหมือน โทรหาตรง — JS คุยกับ native โดยตรง ได้คำตอบทันที ไม่มีตัวกลาง
| แบบเก่า | แบบใหม่ | |
|---|---|---|
| วิธีคุย JS ↔ native | ส่งผ่านตัวกลาง (JSON ไปมา) — ช้า | คุยตรง — เร็ว |
| ผลที่เห็น | ช้ากว่า (พอใช้ได้) | เร็วกว่า เห็นได้ชัดในงานหนัก |
ผลกระทบกับ developer:
- ส่วนใหญ่ของ code เก่ายังใช้ได้ (Expo จัดการ compat)
- บาง 3rd-party library ที่ไม่ update → ต้องใช้ Old Architecture (มี flag)
- รองรับ concurrent React (React โหมดทำงานคู่ขนาน — interrupt update ได้) เต็มที่ (
useTransition,useDeferredValueทำงานเหมือน web)
Prerequisites: ต้องลง Node.js ก่อน (ดาวน์โหลดจาก nodejs.org) —
npmและnpxจะมาพร้อมกันnpx= คำสั่งรัน package โดยไม่ต้องลงก่อน ต่างจากnpm installตรงที่ดาวน์โหลดมารันแล้วลบทิ้ง
bash
# template default (TypeScript + expo-router)
npx create-expo-app@latest my-app
# หรือเลือก template แบบ blank ถ้าอยากเริ่มเปล่า
npx create-expo-app@latest my-app --template blank-typescript⚠️
-tflag เฉยๆ ไม่ได้ — ต้องใส่ชื่อ template ตามหลัง (--template blank-typescript) หรือไม่ใส่เลย (จะได้ default ที่มี expo-router + TypeScript ครบ) ชื่อ template อาจเปลี่ยนไปตาม SDK version — เช็ครายชื่อ template ที่ใช้ได้จริงด้วยnpx create-expo-app --template(ไม่ใส่ค่าตามหลัง จะขึ้น list ให้เลือก)
bash
cd my-app # เข้าโฟลเดอร์ที่สร้างใหม่
npx expo start # รันใน terminal (VS Code terminal, PowerShell, หรือ Command Prompt ก็ได้)เมื่อรันแล้วจะเห็น QR code ขึ้นใน terminal และ browser เปิดหน้า Expo DevTools อัตโนมัติ → Scan QR ด้วย Expo Go app (ค้นหาว่า "Expo Go" ใน App Store หรือ Google Play Store) → เห็น app ทันที!
💡 Expo Go vs Development Build — เริ่มต้นใช้ Expo Go พอ (ครอบคลุม Checkpoint 12.1-12.3 ทั้งหมด)
Expo Go ใช้ได้แค่ library ที่ Expo bundle (รวมไว้ใน app) ไว้แล้ว (camera, location, async storage — ครบสำหรับ app ทั่วไป) ถ้าต้อง native module ที่ไม่อยู่ใน Expo Go (
react-native-firebase, MMKV, library ที่มี custom native code = โค้ดเฉพาะของ platform เช่น Swift/Kotlin) → ต้องใช้ Development Build แทน (ละเอียดในส่วน Deploy):bashnpx expo install expo-dev-client eas build --profile development --platform ios # หรือ androidหลังจาก build เสร็จ ติดตั้ง .ipa/.apk ลงเครื่อง →
npx expo start --dev-clientก็จะใช้ Development Build แทน Expo Go (ได้ทุก native module + ยัง hot reload)
4. โครงสร้าง Expo (App Router)
Expo รุ่นใหม่ใช้ file-based routing — ชื่อไฟล์ในโฟลเดอร์ app/ กลายเป็น route อัตโนมัติ: index.tsx = หน้าแรก, about.tsx = หน้า /about, _layout.tsx = layout ของกลุ่มหน้านั้น ไม่ต้องเขียน route config เอง (คล้าย Next.js สำหรับคนที่รู้ Next.js อยู่แล้ว):
text
my-app/
├── app/ ← file-based routing (เหมือน Next.js)
│ ├── _layout.tsx ← root layout
│ ├── index.tsx ← / (home)
│ └── (tabs)/ ← tab navigation
│ ├── _layout.tsx
│ ├── home.tsx
│ └── settings.tsx
├── components/
├── assets/
└── app.json ← configPart 2: Component พื้นฐาน
5. View + Text + StyleSheet
หมายเหตุ: ไฟล์ React Native ใช้นามสกุล
.tsx= TypeScript + JSX (ถ้าอยากรู้เรื่อง TypeScript เพิ่มเติมดูบท 13) ถ้าไม่ใช้ TypeScript ใช้.jsxแทนได้
tsx
import { View, Text, StyleSheet } from 'react-native';
export default function Home() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello, React Native!</Text>
<Text style={styles.subtitle}>From your phone</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 8,
},
subtitle: {
fontSize: 14,
color: '#666',
},
});สำคัญ:
- ทุก text ต้องอยู่ใน
<Text>(ไม่มี text node ลอย ๆ) - ไม่มี
<div>, ไม่มี<span>— ใช้<View>,<Text> - Style = object (camelCase) ไม่ใช่ CSS string
- ไม่มี cascading (ไม่มีการสืบทอด style จาก parent ไป child — ใน CSS ของเว็บ ถ้าตั้ง
color: redที่<body>ลูกทุกตัวจะได้สีแดงตามอัตโนมัติ แต่ใน React Native ต้องส่ง style ให้แต่ละ component เอง ตั้งที่ parent แล้วลูกไม่ได้รับ)
📖 cascading = การสืบทอด style จาก parent → child โดยอัตโนมัติ (มีใน CSS web, ไม่มีใน React Native)
- Default
flexDirection: 'column'(ต่างจาก web)
6. Layout — Flexbox
React Native จัด layout ด้วย flexbox เหมือน web แต่มีจุดต่างที่ต้องระวัง — default flexDirection เป็น column (web เป็น row), flex: 1 หมายถึง "กินพื้นที่ที่เหลือ" และไม่มี grid ความเข้าใจผิดเรื่อง default direction ทำให้ layout เพี้ยนบ่อยตอนมาจาก web:
tsx
<View style={{ flex: 1, flexDirection: 'row', gap: 8 }}>
<View style={{ flex: 1, backgroundColor: 'red' }} />
<View style={{ flex: 2, backgroundColor: 'blue' }} />
<View style={{ flex: 1, backgroundColor: 'green' }} />
</View>7. Image
แสดงรูปด้วย <Image> ที่รับ source ได้ทั้ง remote ({ uri }) และ local (require) — จุดต่างจาก web คือต้องระบุ width/height เสมอ (ไม่มี auto จาก HTML) ปี 2026 นิยม expo-image ที่เร็วกว่าและมี cache + blurhash placeholder:
tsx
import { Image } from 'react-native';
// Remote
<Image
source={{ uri: 'https://example.com/photo.jpg' }}
style={{ width: 200, height: 200 }}
/>
// Local
<Image source={require('./assets/logo.png')} style={{ width: 100, height: 100 }} />ปี 2026 ใช้ expo-image (เร็วกว่า + cache + blurhash)
8. Touchable / Pressable — แทน button
tsx
import { Pressable, Text } from 'react-native';
<Pressable
onPress={() => alert('clicked')}
style={({ pressed }) => ({
backgroundColor: pressed ? '#0050cc' : '#0066ff',
padding: 12,
borderRadius: 8,
})}
>
<Text style={{ color: 'white', textAlign: 'center' }}>Click me</Text>
</Pressable>Pressable = แทน Touchable* ทุกชนิด (TouchableOpacity = กด-แล้วจาง, TouchableHighlight, TouchableNativeFeedback, TouchableWithoutFeedback) ซึ่งทั้งหมดถือเป็น legacy ใน RN ปัจจุบัน — Pressable รองรับ feedback ละเอียดกว่าและใช้งานได้ในทุกกรณี
9. TextInput
TextInput คือ input ของ React Native — ใช้ value + onChangeText (ไม่ใช่ onChange แบบ web) จุดสำคัญคือ keyboardType ที่เลือก keyboard ให้เหมาะกับข้อมูล (email/numeric/phone) ช่วย UX บนมือถือมาก และ secureTextEntry สำหรับ password:
tsx
import { TextInput, View } from 'react-native';
import { useState } from 'react';
export default function NameInput() {
const [text, setText] = useState('');
return (
<View>
<TextInput
value={text}
onChangeText={setText}
placeholder="Enter name"
style={{ borderWidth: 1, padding: 8, borderRadius: 4 }}
keyboardType="default" // ใช้ "default" สำหรับ text ทั่วไป, "numeric"/"email-address"/"phone-pad" เมื่อรับข้อมูลชนิดนั้น
autoCapitalize="none"
secureTextEntry={false} // true = password
/>
</View>
);
}10. ScrollView vs FlatList
tsx
// ScrollView — เลื่อนได้ แต่ render ทุก item (ใช้กับ list สั้น < 50 รายการ)
import { ScrollView } from 'react-native';
function ItemList({ items }: { items: Array<{ id: number }> }) {
return (
<ScrollView>
{items.map(item => <Item key={item.id} {...item} />)}
</ScrollView>
);
}⚠️ ScrollView render ทุก item พร้อมกัน — ใช้กับ list <50 ตัว
tsx
// FlatList — virtualized (render เฉพาะ item ที่มองเห็นบนจอ ส่วนที่เลื่อนพ้นไปจะถูกลบออกชั่วคราว)
// → ประหยัด memory ทำให้ list ยาวมาก ๆ ไม่กระตุก
import { FlatList } from 'react-native';
// items, loadMore, isRefreshing, refresh มาจาก state/props ของ component
function ItemList({ items, loadMore, isRefreshing, refresh }: {
items: Array<{ id: number }>;
loadMore: () => void;
isRefreshing: boolean;
refresh: () => void;
}) {
return (
<FlatList
data={items}
keyExtractor={item => String(item.id)}
renderItem={({ item }) => <Item {...item} />}
onEndReached={loadMore}
refreshing={isRefreshing}
onRefresh={refresh}
/>
);
}FlatList = render เฉพาะที่เห็น + pull-to-refresh + infinite scroll
11. SafeAreaView — handle notch + status bar
มือถือสมัยใหม่มี notch, status bar และ home indicator ที่อาจบังเนื้อหา — SafeAreaView ครอบเนื้อหาให้อยู่ในพื้นที่ปลอดภัย (เว้นขอบให้พ้นส่วนเหล่านั้น) อัตโนมัติตามอุปกรณ์ ลืมใช้แล้วข้อความจะโดน notch บังบน iPhone:
หมายเหตุ:
react-native-safe-area-contextมาพร้อม Expo SDK แล้ว ไม่ต้อง install เพิ่ม แต่ต้องครอบ root ของ app ด้วย<SafeAreaProvider>ใน_layout.tsxก่อนใช้งานSafeAreaViewถึงจะทำงานถูกต้อง
tsx
// app/_layout.tsx — ต้องใส่ SafeAreaProvider ที่ root ก่อน
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function RootLayout() {
return <SafeAreaProvider>{/* ... */}</SafeAreaProvider>;
}tsx
import { SafeAreaView } from 'react-native-safe-area-context';
<SafeAreaView style={{ flex: 1 }}>
<Text>เนื้อหาไม่โดน notch บัง</Text>
</SafeAreaView>iPhone X+ มี notch — ใช้ SafeAreaView ครอบ
Part 3: Navigation
12. Expo Router (file-based)
navigation ใน React Native เคยต้อง config เอง (React Navigation) แต่ Expo Router ทำให้ใช้ file-based routing เหมือน Next.js — ไฟล์ใน app/ กลายเป็น route, [id].tsx คือ dynamic route, _layout.tsx คือ layout เขียนน้อยลงและคุ้นมือคนที่รู้ Next:
ก่อนอื่น ทำไม mobile app ถึงต้องมีระบบ navigation แยกต่างหาก? ลองนึกดูว่าถ้าใช้ if/else render component ธรรมดา เมื่อกดปุ่มกลับ (back) บน Android หรือปัดจากขอบซ้ายบน iOS ระบบจะไม่รู้ว่า "หน้าก่อนหน้า" คืออะไร เพราะ if/else ไม่ได้เก็บประวัติไว้
Stack Navigator คือกองจาน — เปิดหน้าใหม่ = วางจานใหม่บนกอง, กด Back = เอาจานบนสุดออก ระบบจึงรู้ว่าต้อง slide กลับไปที่ไหน และ animation เข้า-ออกก็ทำงานถูกต้อง
Expo Router ห่อ Stack Navigator นั้นไว้ภายใน แล้วให้เราแค่สร้างไฟล์ — ชื่อไฟล์กลายเป็นชื่อหน้าโดยอัตโนมัติ
ใหม่ + ใช้ง่าย — เหมือน Next.js
text
app/
├── _layout.tsx ← root layout
├── index.tsx ← /
├── about.tsx ← /about
├── posts/
│ ├── index.tsx ← /posts
│ └── [id].tsx ← /posts/123
└── (tabs)/
├── _layout.tsx ← tab navigator
├── home.tsx
└── settings.tsxLayout — Stack Navigator
tsx
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="about" options={{ title: 'About' }} />
</Stack>
);
}Tab Layout
tsx
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
export default function TabsLayout() {
return (
<Tabs>
<Tabs.Screen name="home" options={{ title: 'Home' }} />
<Tabs.Screen name="settings" options={{ title: 'Settings' }} />
</Tabs>
);
}Navigate
tsx
import { Link, router } from 'expo-router';
<Link href="/about">Go to About</Link>
<Link href={`/posts/${post.id}`}>{post.title}</Link>
// programmatic
router.push('/about');
router.replace('/login');
router.back();Dynamic Route
tsx
// app/posts/[id].tsx
import { useLocalSearchParams } from 'expo-router';
export default function PostDetail() {
const { id } = useLocalSearchParams();
return <Text>Post {id}</Text>;
}13. React Navigation (alternative)
ทำไมสอน React Navigation ทั้งที่แนะนำ Expo Router? — เพราะ codebase เก่าในงานจริงส่วนใหญ่ยังใช้ React Navigation อยู่ การอ่านและแก้โค้ดเก่าต้องรู้จัก pattern นี้ สำหรับ project ใหม่ใช้ Expo Router ดีกว่า
ก่อน Expo Router — มาตรฐานคือ @react-navigation/native
tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Detail" component={Detail} />
</Stack.Navigator>
</NavigationContainer>ปี 2026 — Expo Router ดีกว่าสำหรับ project ใหม่
Part 4: Style Library
14. NativeWind — Tailwind สำหรับ React Native
Optional section — เหมาะสำหรับคนที่รู้ Tailwind CSS อยู่แล้ว ถ้ายังไม่รู้ Tailwind ข้ามไปก่อนได้
Tailwind CSS = utility class library สำหรับ CSS — แทนที่จะเขียน { color: 'blue', padding: 16 } ก็ใช้ class สั้น ๆ เช่น text-blue-500 p-4 แทน ทำให้ style เร็วและ consistent NativeWind นำ Tailwind มาใช้ใน React Native ด้วย prop className:
ถ้าคุ้นกับ Tailwind จาก web ก็ใช้ต่อใน React Native ได้ด้วย NativeWind — เขียน className แบบเดียวกัน ทำให้ style consistent ระหว่าง web กับ mobile และไม่ต้องเขียน StyleSheet object ยาว ๆ:
bash
npm install nativewind tailwindcss⚠️ NativeWind ต้องการ setup เพิ่มเติม — ไม่ใช่แค่ install แล้วใช้ได้เลย ต้องตั้งค่า
babel.config.js,metro.config.js,tailwind.config.jsและ import global CSS ใน layout ด้วย ดู setup ครบที่ nativewind.dev/getting-started/expo-router เพราะ setup อาจเปลี่ยนตาม Expo SDK version
tsx
<View className="flex-1 items-center justify-center bg-white">
<Text className="text-2xl font-bold mb-2">Hello!</Text>
<Pressable className="bg-blue-500 px-4 py-2 rounded">
<Text className="text-white">Click</Text>
</Pressable>
</View>ใช้ class Tailwind ได้ — ดี + consistent กับ web
15. Tamagui
อีกทางเลือกคือ Tamagui — UI kit + style system ที่ออกแบบมาเพื่อ React Native โดยเฉพาะ มี compiler optimize style ตอน build จึง render เร็วกว่า NativeWind และมาพร้อม component สำเร็จ เหมาะถ้าต้องการ performance สูงและ component library ในตัว:
UI kit + style system ที่ optimize สำหรับ React Native (เร็วกว่า NativeWind)
tsx
// Tamagui shorthand props: ai = alignItems, jc = justifyContent, f = flex
<YStack ai="center" jc="center" f={1}>
<H1>Hello</H1>
<Button onPress={() => console.log('clicked')}>Click</Button>
</YStack>Part 5: API + State
16. Fetch + TanStack Query
ข่าวดี: data fetching ใน React Native ใช้ TanStack Query เหมือน web เป๊ะ — โค้ด query/cache เหมือนกันทุกอย่าง ต่างแค่ component ที่แสดงผล (<FlatList>, <ActivityIndicator> แทน HTML) ทำให้ logic ฝั่ง data reuse ข้าม platform ได้:
bash
npm install @tanstack/react-queryต้องครอบ app ด้วย
<QueryClientProvider>ก่อนใช้งาน (เหมือนบท web — ถ้ายังไม่คุ้นดูบทที่สอน TanStack Query ก่อน):tsximport { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const queryClient = new QueryClient(); export default function App() { return <QueryClientProvider client={queryClient}>{/* ... */}</QueryClientProvider>; }
ใช้เหมือน web:
tsx
import { useQuery } from '@tanstack/react-query';
import { ActivityIndicator, FlatList, Text } from 'react-native';
type User = { id: number; name: string };
function UserList() {
const { data, isLoading } = useQuery<User[]>({
queryKey: ['users'],
queryFn: () => fetch('https://api.example.com/users').then(r => r.json())
});
if (isLoading) return <ActivityIndicator />;
return (
<FlatList
data={data}
keyExtractor={u => String(u.id)}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
);
}17. AsyncStorage / MMKV — เก็บ data ใน device
useState เก็บข้อมูลในหน่วยความจำ (RAM) — ปิดแอปแล้วข้อมูลหายทันที ลองนึกว่าเขียนโน้ตบนกระดาษทราย แค่พัดลมเป่าก็หาย AsyncStorage คือกล่องเก็บของในมือถือที่ยังอยู่แม้ปิดแอปและรีสตาร์ทเครื่อง คล้าย localStorage ของเว็บ แต่ API เป็น async เพราะอ่านจาก disk (ช้ากว่า RAM) จึงต้องรอด้วย await ลองนึกตัวอย่าง: เก็บชื่อ user ไว้ใน AsyncStorage — ปิดแอปแล้วเปิดใหม่ ชื่อยังอยู่ ไม่ต้องล็อกอินใหม่
AsyncStorage (basic):
tsx
import AsyncStorage from '@react-native-async-storage/async-storage';
await AsyncStorage.setItem('token', 'xyz');
const token = await AsyncStorage.getItem('token');
await AsyncStorage.removeItem('token');⭐
react-native-mmkv= แนะนำปี 2026 — sync API + เร็วกว่า AsyncStorage 30 เท่า⚠️ MMKV ต้องใช้ Development Build เท่านั้น — ใช้กับ Expo Go ไม่ได้ เพราะมี native module พิเศษ ถ้ายังใช้ Expo Go อยู่ให้ใช้ AsyncStorage ไปก่อน
tsx
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
storage.set('token', 'xyz'); // sync!
const token = storage.getString('token'); // sync!
storage.delete('token');สำหรับ token + auth — ใช้
expo-secure-store(encrypted = เข้ารหัส — ข้อมูลถูกแปลงให้อ่านไม่ออกถ้าไม่มีกุญแจ) ด้วย Keychain ของ iOS / Keystore ของ Android (ระบบเก็บ secret ของแต่ละ OS)
Part 6: Native Features
18. Camera + Photo
จุดเด่นของ mobile คือเข้าถึง native feature ได้ — Expo มี module สำเร็จให้เรียกใช้ง่าย เริ่มจากกล้อง/รูป: expo-image-picker เปิด gallery หรือกล้องให้ user เลือกรูป (จัดการ permission ให้แล้ว) คืน uri มาใช้ต่อ:
bash
npx expo install expo-image-pickertsx
import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
const [image, setImage] = useState<string | null>(null);
async function pickImage() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'], // SDK 51+ syntax (เดิม MediaTypeOptions.Images — deprecated)
allowsEditing: true,
quality: 0.8,
});
if (!result.canceled) {
setImage(result.assets[0].uri);
}
}⚠️ API ของ
mediaTypesเปลี่ยนหลายครั้งตาม Expo SDK version (จากMediaTypeOptions.Images→ array of strings อย่างที่เห็นด้านบน) — ถ้าโค้ดพัง ให้ตรวจสอบ syntax ล่าสุดตาม SDK version ที่ใช้จริงใน expo-image-picker docs
19. Location
เข้าถึงตำแหน่ง GPS ด้วย expo-location — จุดสำคัญคือต้องขอ permission จาก user ก่อน (requestForegroundPermissionsAsync) แล้วค่อยอ่านพิกัด นี่เป็น pattern ของทุก native feature: ขอสิทธิ์ → เช็ค status → ใช้งาน:
bash
npx expo install expo-locationtsx
import * as Location from 'expo-location';
import { Alert } from 'react-native';
const { status } = await Location.requestForegroundPermissionsAsync();
if (status === 'granted') {
const location = await Location.getCurrentPositionAsync({});
console.log(location.coords.latitude, location.coords.longitude);
} else {
Alert.alert('ต้องการสิทธิ์ GPS', 'ไปเปิดใน Settings > แอปของคุณ > Location');
}- pattern นี้เหมือนกันทุก native feature — camera, microphone, contacts — ถ้า
status !== 'granted'ต้อง fallback หรือแจ้ง user เสมอ อย่าปล่อยให้ app เงียบหายไปเฉยๆ เพราะ user จะไม่รู้ว่าเกิดอะไรขึ้น
20. Push Notification
push notification เป็นฟีเจอร์ที่ mobile ทำได้แต่ web ทำยาก ฝั่ง iOS ใช้ระบบชื่อ APNs (Apple Push Notification service — บริการส่ง notification ของ Apple) ส่วนฝั่ง Android ใช้ FCM (Firebase Cloud Messaging — บริการส่ง notification ของ Firebase ซึ่งเป็นบริการ backend สำเร็จรูปของ Google) ทั้งสองระบบนี้ setup เองยุ่งพอสมควร Expo จึงมี push service ฟรีที่ห่อความยุ่งของทั้งคู่ไว้ ให้ส่ง notification ข้าม platform ผ่าน API เดียว:
ลองนึก push notification เหมือนระบบไปรษณีย์ถึงบ้าน — มือถือของ user ต้องแจ้ง Expo server ก่อนว่า "บ้านฉันอยู่ที่นี่" นั่นคือ Expo push token (รหัสประจำเครื่องของ user คนนั้น) จากนั้น server ของคุณส่ง notification ผ่าน Expo ไปถึง APNs/FCM ซึ่งส่งถึงมือถือปลายทาง ขั้นตอนแรกที่ต้องทำเสมอคือขอ permission แล้วรับ token:
tsx
import * as Notifications from 'expo-notifications';
// ขอ permission ก่อน — ถ้าไม่ขอ notification จะไม่แสดงบน iOS
const { status } = await Notifications.requestPermissionsAsync();
// รับ token ประจำเครื่องของ user คนนี้
const token = await Notifications.getExpoPushTokenAsync();
// ส่ง token ไปเก็บที่ server ของคุณ เพื่อใช้ส่ง notification ในอนาคต
console.log(token.data); // "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"bash
npx expo install expo-notificationsใช้ Expo's push service (ฟรี) — handle iOS + Android ในที่เดียว
21. Other Expo Modules
| Module | ทำอะไร |
|---|---|
expo-camera | ถ่ายรูป |
expo-haptics | สั่น (haptic feedback) |
expo-sensors | accelerometer, gyroscope |
expo-secure-store | encrypted storage (token, password) |
expo-file-system | อ่าน/เขียน file ใน app sandbox |
expo-sharing | share content ไป app อื่น |
expo-linking | deep link |
expo-localization | i18n |
expo-av | audio + video |
Part 7: Deploy
22. Build with EAS
การ build app native ปกติต้องมีเครื่องมือเฉพาะ (Xcode บน Mac สำหรับ iOS) — EAS Build คือบริการ cloud ของ Expo ที่ build ให้บน server ทำให้ build iOS ได้แม้ไม่มี Mac แค่สั่งจาก CLI:
Prerequisites: ต้องสมัครบัญชีที่ expo.dev ก่อน (
eas loginจะล้มเหลวถ้าไม่มี account)
bash
npm install -g eas-cli # -g = global install (ลงครั้งเดียว ใช้ได้ทุก project)
eas login
eas build --platform ios
eas build --platform androidEAS Build = cloud service ของ Expo ที่ build ให้ — ไม่ต้องมี Mac สำหรับ iOS
23. Submit to Store
หลัง build เสร็จก็ส่งขึ้น store ด้วย eas submit ที่ upload ให้อัตโนมัติ — แต่ต้องมีบัญชี developer ก่อน (Apple $99/ปี, Google $25 ครั้งเดียว) นี่เป็นต้นทุนคงที่ที่ต้องวางแผนก่อนปล่อย app จริง:
bash
eas submit --platform ios
eas submit --platform androidต้องมี:
- iOS: Apple Developer account ($99/year ≈ 3,600 บาท ตามอัตราปัจจุบัน — ชำระผ่านบัตรเครดิต/เดบิต international)
- Android: Google Play account ($25 one-time ≈ 900 บาท ชำระครั้งเดียว)
24. OTA Update — เปลี่ยน JS โดยไม่ต้อง submit ใหม่
bash
eas updateUser เปิด app ครั้งต่อไป → download JS bundle ใหม่ → app เปลี่ยน
ไม่ต้องรอ Apple review (1-2 วัน) — perfect สำหรับ fix bug ด่วน
⚠️ ใช้ได้กับ JS only — ถ้าเปลี่ยน native module ต้อง build + submit ใหม่
⚠️ ข้อจำกัด Apple: OTA update ใช้ได้กับ bug fix และ content update เท่านั้น — ห้ามใช้เปลี่ยน core functionality ของ app อย่างมีนัยสำคัญ (ผิด App Store Review Guidelines 2.5.2 อาจโดน reject หรือ remove ออกจาก store) Expo EAS Update ระบุข้อจำกัดนี้ใน docs ของตัวเองด้วย
Part 8: ⚠️ React Native vs Web — สิ่งที่ต่าง
| Web | React Native | |
|---|---|---|
| HTML element | <div>, <p>, <button> | <View>, <Text>, <Pressable> |
| CSS | string | object |
<Text> ต้องครอบทุก text | ไม่ | ✅ ใช่ |
| Default flex direction | row | column |
| Cascade style | ใช่ | ไม่ |
| Hover | ใช่ | ไม่ (mobile) |
| Routing | React Router | Expo Router / React Navigation |
| localStorage | ใช่ | AsyncStorage |
| Window / Document | ใช่ | ไม่ |
| CSS animation | ใช่ | Reanimated / Moti |
25. Animation — Reanimated
animation บน mobile ต้องลื่น 60fps
ทำไมต้องใช้ Reanimated? — React Native มี 2 thread:
- JS thread = รัน JavaScript ของเรา (logic, state, API call)
- UI thread = native ใช้วาดหน้าจอ (ต้องว่างเสมอเพื่อให้ลื่น)
ถ้า animate บน JS thread แล้ว JS thread busy (กำลัง fetch data ฯลฯ) → animation กระตุก Reanimated ย้าย animation ไปรันบน UI thread โดยตรง → ลื่น 60fps แม้ JS thread ยุ่ง
ถ้าใช้ useState เลื่อนกล่องจะเกิดอะไร? — setState → React re-render → JS thread คำนวณ style ใหม่ → ส่งผลไปยัง UI thread ทุก frame แต่ถ้า JS thread กำลังยุ่งอยู่ (เช่น fetch ข้อมูล) frame ถัดไปก็ต้องรอ ผลคือกระตุก useSharedValue ต่างตรงที่ค่านี้ "อาศัยอยู่" บน UI thread ตลอดเวลา JS thread ส่งคำสั่ง "เคลื่อนไป 150" ครั้งเดียวแล้วจบ UI thread จัดการ interpolate ทุก frame เองโดยไม่ต้องรอ JS อีก ยุ่งแค่ไหนก็ไม่กระทบ
ใช้แค่ 3 hook: useSharedValue (เก็บค่าที่ animate) + useAnimatedStyle (แปลงเป็น style) + withSpring/withTiming (ทำให้เด้ง/ลื่น):
bash
npx expo install react-native-reanimatedtsx
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
function Box() {
const offset = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
}));
return (
<>
<Animated.View style={[styles.box, animatedStyle]} />
<Pressable onPress={() => { offset.value = withSpring(Math.random() * 200); }}>
<Text>Move</Text>
</Pressable>
</>
);
}Reanimated run animation บน UI thread → smooth 60 FPS แม้ JS thread busy
26. ⚠️ Common Pitfalls
| Pitfall | แก้ |
|---|---|
Text นอก <Text> | wrap ใน <Text> |
Style เป็น string "color: red" | object { color: 'red' } |
| ScrollView กับ list ใหญ่ | ใช้ FlatList |
| ไม่ handle SafeArea | ใช้ SafeAreaView หรือ useSafeAreaInsets() |
| Sync API ของ web (localStorage) | ใช้ async (AsyncStorage) |
| Animation บน JS thread | ใช้ Reanimated (UI thread) |
| Build แบบเดียวสำหรับ iOS + Android | test 2 platform — ต่างกัน |
| Skip permission request | ต้อง request ก่อนใช้ camera/location |
27. Checkpoint
📝 หมายเหตุ: checkpoint บทนี้ ตั้งใจไม่มีเฉลย ให้ลองทำเองก่อน ถ้าติดให้ย้อนไปดูตัวอย่างโค้ดในบทแล้วดัดแปลง
🛠️ Checkpoint 12.1 — Hello Native
สร้าง Expo app → run บนมือถือ → แสดง "Hello, [your name]"
เพิ่ม Pressable ที่ alert เมื่อกด
🛠️ Checkpoint 12.2 — Note App
- หน้า list ของ note (FlatList)
- หน้า add (TextInput + save)
- เก็บใน AsyncStorage
- ลบได้ด้วย swipe (เพิ่ม
react-native-gesture-handler— ต้องใส่import 'react-native-gesture-handler'ที่ไฟล์ root ก่อน และใช้ร่วมกับSwipeablecomponent หรือดู docs สำหรับ swipe-to-delete)
🛠️ Checkpoint 12.3 — Camera
- กดถ่ายรูป → preview
- save ลง gallery
- มี gallery list ของรูปที่ถ่าย
🛠️ Checkpoint 12.4 — Build to Store
ใช้ EAS build APK สำหรับ Android → install ลงมือถือเอง (ไม่ต้อง submit Play Store ก็ได้)
28. สรุปบท
✅ React Native = "เขียน React → render native UI" (ไม่ใช่ WebView)
✅ Expo = framework ที่ทำให้ React Native ใช้ง่ายปี 2026
✅ Expo Go = QR scan → run บนมือถือทันที (เร็ว, ไม่ต้อง Xcode/Android Studio)
✅ Component ต่าง web: <View>, <Text>, <Pressable>, <TextInput>, <Image>, <FlatList>, <SafeAreaView>
✅ Style = object (camelCase), no cascade, default flex column
✅ Expo Router = file-based routing (เหมือน Next.js)
✅ NativeWind = Tailwind สำหรับ RN
✅ TanStack Query + Zustand (state management library จากบทที่ 7 — ใช้กับ React Native ได้เหมือน web เลย ไม่มีอะไรพิเศษ) ใช้ได้เหมือนเดิม
✅ EAS = build + submit + OTA update ใน cloud
✅ Reanimated สำหรับ smooth animation (UI thread)
29. React Native Web — รัน RN code บน web
react-native-web แปลง <View>, <Text>, <Pressable> → <div>, <span>, <button> ใน browser
bash
npx expo install react-native-web @expo/metro-runtime
npx expo start --web→ app เดียวกัน run ได้ทั้ง iOS + Android + Web — universal app (code เดียวรันได้ทุก platform)
ใช้เมื่อ: ต้องการ share UI ระหว่าง mobile + web (เช่น Meta Ads Manager ที่ระบุใน React Native Web official docs)
➡️ บทถัดไป
ยังไม่จบ — บทที่ 13-15 เป็น advanced track:
- 13 TypeScript Deep — generic component, polymorphic, exhaustive check
- 14 React Internals — Fiber, hooks linked list, concurrent rendering
- 15 Accessibility + i18n + SEO
ถ้าจะเขียน production app — ไปต่อบท 13-15
🎉 จบ React Native track
หลังจากบทที่ 0-12 คุณมีพื้นฐานเต็ม:
- Foundation (intro, JSX, component, props, state, event)
- Hooks deep dive
- Forms + Validation
- Data Fetching (TanStack Query)
- Routing
- Styling + UI Libraries (Tailwind, shadcn, Radix)
- State Management (Zustand, Jotai, Redux Toolkit)
- Testing (Vitest, RTL, Playwright)
- Advanced Patterns (Portal, ErrorBoundary, Suspense, Compound)
- Animation + Performance (Motion, virtualization)
- Next.js (SSR, Server Components, Server Actions)
- React Native (mobile)
ก้าวต่อไป
| ทำอะไร | |
|---|---|
| TanStack Router / Start | type-safe routing + full-stack |
| GraphQL (Apollo / urql) | alternative API |
| TanStack DB | client-side database |
| WebAssembly | high-perf code in browser |
| Three.js + React Three Fiber | 3D ใน React |
| Solid.js / Qwik | competitor framework — น่าศึกษา |
| AI / LLM integration | Vercel AI SDK |