Expo 環境建置與 RN 核心元件

如果你已經會寫 HTML、CSS 和 React(或是 Next.js),那麼恭喜你,你已經具備了打造 iOS 和 Android 原生 App 的能力!

過去,寫 iOS App 必須學 Swift 和 Xcode,寫 Android 必須學 Kotlin 和 Android Studio。要維護兩個完全不同的程式碼庫,對獨立開發者來說是一場惡夢。

React Native (RN) 打破了這個藩籬。它允許你用 JavaScript 寫邏輯,然後底層會自動把這些程式碼編譯成真正原生的手機元件(而不是把網頁塞進 WebView 裡)。

Expo 則是建立在 RN 之上的神級框架。就像 Next.js 之於 React 一樣,Expo 幫你處理了所有最痛苦的底層設定,讓你能在幾分鐘內就在自己的手機上看到熱重載 (Hot Reload) 的 App 畫面!

1. 環境建置:只需一行指令

你不需要安裝幾十 GB 的 Xcode 或 Android Studio。你只需要 Node.js!

打開終端機,輸入:

# 建立一個新的 Expo 專案
npx create-expo-app@latest my-first-app

# 進入資料庫
cd my-first-app

# 啟動開發伺服器
npm run start

這時候,你的終端機會出現一個巨大的 QR Code

如何在手機上預覽?

  1. 拿起你的 iPhone 或 Android 手機。
  2. 在 App Store / Google Play 搜尋並下載 Expo Go 這個 App。
  3. 如果是 iPhone,直接打開內建相機掃描電腦螢幕上的 QR Code;如果是 Android,打開 Expo Go 點擊 Scan QR Code。

神奇的事情發生了!你的手機畫面上出現了 App 的雛形。現在,只要你在電腦上存檔,手機畫面就會在不到一秒內瞬間更新,這就是傳說中的 Hot Reload!

2. 忘掉 HTML 標籤!認識原生元件

在 React Native 的世界裡,沒有 <div>, <span>, <p> 這些標籤,因為手機看不懂 HTML。 你必須從 react-native 中引入專屬的原生元件:

| Web (網頁) | React Native (手機) | 用途 | | :--- | :--- | :--- | | <div> | <View> | 容器、排版區塊 | | <p> / <h1> / <span> | <Text> | 顯示所有文字 | | <button> | <TouchableOpacity> / <Pressable> | 可點擊的按鈕 | | <img> | <Image> | 顯示圖片 | | <input> | <TextInput> | 文字輸入框 | | <div style="overflow: scroll"> | <ScrollView> | 可滾動的視窗 |

讓我們來改寫 app/index.tsx (或是 App.js):

import { StyleSheet, Text, View, Image, TouchableOpacity, Alert } from 'react-native';

export default function App() {
  const handlePress = () => {
    // Alert.alert 會叫出手機系統原生的警告視窗!
    Alert.alert("太棒了", "你成功點擊了按鈕!");
  };

  return (
    <View style={styles.container}>
      <Image 
        source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
        style={styles.logo}
      />
      <Text style={styles.title}>Hello Vibe Tutor!</Text>
      <Text style={styles.subtitle}>這是我的第一個原生 App 📱</Text>
      
      <TouchableOpacity style={styles.button} onPress={handlePress}>
        <Text style={styles.buttonText}>點我測試</Text>
      </TouchableOpacity>
    </View>
  );
}

3. 忘掉 CSS Class!認識 StyleSheet

在 RN 中,所有的樣式都是透過 JavaScript 物件來定義的,使用的是 Flexbox 佈局。

[!WARNING] 佈局地雷警告

  1. RN 的 flexDirection 預設是 column (直向排列),而網頁 CSS 預設是 row (橫向)!
  2. 所有尺寸都沒有單位(不能寫 pxrem),直接寫數字 width: 100
  3. 不支援部分複雜的 CSS 屬性(如 box-shadow 的寫法會被拆成好幾個屬性)。

我們來看看上面例子的樣式表:

const styles = StyleSheet.create({
  container: {
    flex: 1, // 佔滿整個螢幕
    backgroundColor: '#fff',
    alignItems: 'center', // 左右置中
    justifyContent: 'center', // 上下置中
  },
  logo: {
    width: 100,
    height: 100,
    marginBottom: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    marginTop: 8,
    marginBottom: 32,
  },
  button: {
    backgroundColor: '#3b82f6',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  }
});

[!TIP] 想要用 Tailwind CSS 嗎? 你絕對可以!透過安裝 NativeWind 這個套件,你就可以在 RN 裡面寫 <View className="flex-1 items-center justify-center bg-white"> 這種熟悉的語法了!這對 Web 開發者來說是極大的福音。

4. SafeAreaView:拯救你的瀏海與動態島

當你把上面的程式碼放到 iPhone 上執行時,你可能會發現文字被上面的「瀏海」或是「時間列」擋住了!

在手機開發中,我們必須尊重系統的「安全區域」。請養成好習慣,將最外層的容器換成 <SafeAreaView>

import { SafeAreaView } from 'react-native-safe-area-context';

export default function App() {
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}>
      <View style={styles.container}>
        {/* ...內容... */}
      </View>
    </SafeAreaView>
  );
}

這樣一來,不管你的 App 是跑在有瀏海的 iPhone 14、還是有動態島的 iPhone 15 Pro,你的 UI 都會被完美地推擠到安全的可視範圍內!

在下一章中,我們將學習如何打造 App 中最重要的骨幹:多頁面導覽 (Navigation)