在 Day 9 設計後端 Prompt 範本時,我們預留了 {{TARGET_RACE}}(目標賽事)與 {{CURRENT_LEVEL}}(體能現況)等動態參數。
要讓雲端的 AI 教練給出真正客製化的課表,App 必須先收集跑者的基本數據(身高、體重、每週可練跑天數與目標)。

在 Web 中,表單是 <form> 包裹 <input>;但在 React Native 中沒有 form 概念,且存在手機端最常見的「虛擬鍵盤遮擋」問題。今天將以最佳實踐完成防雷表單。
<form> 標籤,狀態全由 useState 控制:各欄位值由 React State 即時同步,點擊送出按鈕時直接讀取 State。onChangeText 取代 onChange:直接接收輸入的字串值,無需透過 e.target.value 提取。KeyboardAvoidingView 與 ScrollView 協同處理。Prompt:
「我正在開發 KAKERU 馬拉松 APP,請幫我以最佳實踐實作個人設定頁面。
資料欄位 (使用 useState 管理):
暱稱、目標賽事 (字串)
身高、體重、每週練跑天數 (數字)
UI 與架構要求:
使用 KeyboardAvoidingView 與 ScrollView,點擊空白處可收起鍵盤。
身高與體重欄位並排,並顯示單位 (cm/kg)。
底部需保留 safe inset 空間,確保送出按鈕不被底部懸浮導覽列遮擋。
品牌色為亮橘色 (#FF5722),卡片採圓角與淡灰邊框設計。」
產生的程式碼:
export default function ProfileScreen() {
const insets = useSafeAreaInsets();
// 1. 宣告 State 收集跑者參數
const [nickname, setNickname] = useState('');
const [height, setHeight] = useState('');
const [weight, setWeight] = useState('');
const [targetRace, setTargetRace] = useState('');
const [weeklyDays, setWeeklyDays] = useState('');
const handleSave = () => {
const runnerData = { nickname, height: Number(height), weight: Number(weight), targetRace, weeklyDays: Number(weeklyDays) };
console.log('準備傳送給 AI 教練的資料:', runnerData);
Alert.alert('儲存成功', '跑者資料已就緒,準備於明日串接 AI 課表生成!');
};
return (
<SafeAreaView style={styles.safeArea} edges={['top', 'left', 'right']}>
{/* 2. 關鍵防雷:鍵盤推升與點擊空白處收合 */}
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<ScrollView
contentContainerStyle={[
styles.scrollContent,
{ paddingBottom: insets.bottom + 100 } // 避開底部懸浮導覽列
]}
keyboardShouldPersistTaps="handled"
>
{/* 3. 表單卡片輸入區 */}
<View style={styles.card}>
<Text style={styles.label}>跑者暱稱</Text>
<TextInput style={styles.input} placeholder="例如:Kakeru" value={nickname} onChangeText={setNickname} />
{/* 身高與體重並排 */}
<View style={styles.row}>
<View style={styles.halfField}>
<Text style={styles.label}>身高 (cm)</Text>
<TextInput style={styles.input} keyboardType="numeric" placeholder="175" value={height} onChangeText={setHeight} />
</View>
<View style={styles.halfField}>
<Text style={styles.label}>體重 (kg)</Text>
<TextInput style={styles.input} keyboardType="numeric" placeholder="68" value={weight} onChangeText={setWeight} />
</View>
</View>
{/* ... 目標賽事與每週天數輸入框 (結構同上,略) ... */}
</View>
{/* 4. 送出按鈕 */}
<TouchableOpacity style={styles.saveButton} onPress={handleSave}>
<Text style={styles.saveButtonText}>儲存並準備生成課表</Text>
</TouchableOpacity>
</ScrollView>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
// ... 樣式表 StyleSheet (省略) ...
TouchableWithoutFeedback + Keyboard.dismiss):使用者點擊畫面任一空白處即可自然收起鍵盤。keyboardShouldPersistTaps="handled":避免使用者點擊按鈕時因鍵盤收起而被吞掉點擊事件。useSafeAreaInsets 計算底部邊距,避免懸浮 Tab Bar 遮蓋「儲存按鈕」的風險。避免使用者沒有輸入就儲存的防呆機制:
使用者輸入完成後顯示儲存成功:
今天完成了跑者個人資料表單,並處理了鍵盤彈出與安全邊距問題,資料已就緒儲存於本地狀態。明天(Day 17) 前後端大串接!我們將 React Native 現有開發的內容,將跑者資料傳送至 GCP Cloud Run 上的 Spring Boot AI 教練 API,正式打通 AI 課表生成流程!我們明天見!!