请添加图片描述

案例开源地址:https://atomgit.com/nutpi/rn_openharmony_dogimg

表单是信任的桥梁

用户决定领养一只狗狗后,需要填写申请表。这个表单不只是收集信息,更是建立信任的过程。

领养机构需要了解申请人的情况,判断是否适合养狗。申请人通过填写表单,表达自己的诚意和准备。

这篇文章讲 AdoptFormPage 的实现,涉及到表单布局、输入框组件、表单验证等内容。

页面的导入部分

import React from 'react';
import {View, Text, ScrollView, TextInput, TouchableOpacity, StyleSheet} from 'react-native';
import {Header, Card} from '../../components';
import {useNavigation, useRoute} from '../../hooks';
import type {AdoptDog} from '../../types';

这个页面用到了 TextInput,这是 React Native 的文本输入组件,相当于 HTML 的 input 和 textarea。

其他导入和之前的页面类似:Header 做顶部导航,Card 做内容分组,useNavigation 和 useRoute 处理路由。

获取路由参数

export function AdoptFormPage() {
  const {navigate} = useNavigation();
  const {params} = useRoute<{dog: AdoptDog}>();

从详情页跳转过来时,带了 dog 参数。

表单页需要知道用户申请领养的是哪只狗,用于显示和提交。

页面整体结构

return (
  <View style={s.container}>
    <Header title="领养申请" />
    <ScrollView style={s.content}>
      ...
    </ScrollView>
    <View style={s.footer}>
      ...
    </View>
  </View>
);

和详情页一样的三层结构:Header、ScrollView、footer。

表单内容可能很长,需要滚动。提交按钮固定在底部,随时可点。

显示申请的狗狗

<Card>
  <Text style={s.dogName}>申请领养: {params.dog.name}</Text>
</Card>

在表单顶部显示狗狗的名字,让用户确认自己申请的是哪只。

dogName: {fontSize: 16, fontWeight: '500', color: '#D2691E', textAlign: 'center'},

用主题色显示名字,居中对齐,作为表单的"标题"。

fontWeight: '500' 是中等粗细,比普通文字稍重,但不像 ‘600’ 那么粗。

个人信息区域

<Card>
  <Text style={s.sectionTitle}>个人信息</Text>
  <Text style={s.label}>姓名 *</Text>
  <TextInput style={s.input} placeholder="请输入您的姓名" />
  <Text style={s.label}>联系电话 *</Text>
  <TextInput style={s.input} placeholder="请输入联系电话" keyboardType="phone-pad" />
  <Text style={s.label}>居住地址 *</Text>
  <TextInput style={s.input} placeholder="请输入居住地址" />
</Card>

三个必填字段:姓名、电话、地址。

标签后面的 * 表示必填,这是表单设计的通用约定。

每个输入框都有 placeholder 提示用户该填什么。

TextInput 的基本用法

<TextInput style={s.input} placeholder="请输入您的姓名" />

style 设置输入框的样式。

placeholder 是占位文字,输入框为空时显示,用户开始输入后消失。

电话输入框的键盘类型

<TextInput 
  style={s.input} 
  placeholder="请输入联系电话" 
  keyboardType="phone-pad" 
/>

keyboardType="phone-pad" 让弹出的键盘是数字键盘,方便输入电话号码。

常用的 keyboardType 值:

  • default:默认键盘
  • numeric:纯数字键盘
  • phone-pad:电话键盘,有数字和 + - * #
  • email-address:带 @ 和 . 的键盘

选择合适的键盘类型能提升输入效率。

输入框样式

input: {
  backgroundColor: '#f5f5f5', 
  borderRadius: 8, 
  padding: 12, 
  fontSize: 15, 
  color: '#333'
},

backgroundColor: ‘#f5f5f5’ 浅灰色背景,和 Card 的白色背景区分开。

borderRadius: 8 圆角,看起来更柔和。

padding: 12 内边距,文字不贴边。

fontSize: 15 输入文字的大小。

color: ‘#333’ 输入文字的颜色。

标签样式

label: {fontSize: 14, color: '#666', marginBottom: 6, marginTop: 10},

标签用小一号的灰色字体。

marginBottom: 6 和输入框拉开一点距离。

marginTop: 10 和上一个输入框拉开距离,除了第一个标签。

区域标题样式

sectionTitle: {fontSize: 16, fontWeight: '600', color: '#333', marginBottom: 10},

区域标题比标签大一号,加粗,深色。

marginBottom: 10 和下面的内容拉开距离。

养宠经验区域

<Card>
  <Text style={s.sectionTitle}>养宠经验</Text>
  <TextInput 
    style={[s.input, s.textarea]} 
    placeholder="请描述您的养宠经验和家庭情况" 
    multiline 
    numberOfLines={4} 
  />
</Card>

这是个多行输入框,用于填写较长的文字。

multiline 让输入框支持多行输入,相当于 HTML 的 textarea。

numberOfLines={4} 设置默认显示 4 行的高度。

style={[s.input, s.textarea]} 用数组合并两个样式。

多行输入框样式

textarea: {height: 100, textAlignVertical: 'top'},

height: 100 固定高度,比单行输入框高。

textAlignVertical: ‘top’ 让光标和文字从顶部开始。这是 Android 特有的属性,iOS 默认就是顶部对齐。

如果不加这个属性,Android 上的光标会在输入框中间,很奇怪。

领养承诺区域

<Card>
  <Text style={s.sectionTitle}>领养承诺</Text>
  <Text style={s.promise}>• 我承诺会善待领养的狗狗</Text>
  <Text style={s.promise}>• 我承诺定期带狗狗进行健康检查</Text>
  <Text style={s.promise}>• 我承诺不会遗弃狗狗</Text>
</Card>

这不是输入区域,而是展示领养承诺条款。

符号做列表项的标记,简单直观。

promise: {fontSize: 14, color: '#666', lineHeight: 26},

lineHeight: 26 让每行之间有足够的间距,阅读更舒适。

为什么要展示承诺条款

领养不是买卖,是一种责任。展示承诺条款有几个作用:

筛选作用:不认同这些条款的人可能会放弃申请。

教育作用:提醒申请人养狗需要承担的责任。

法律作用:提交申请相当于同意这些条款,有一定的约束力。

提交按钮

<View style={s.footer}>
  <TouchableOpacity style={s.btn} onPress={() => navigate('AdoptDone', {name: params.dog.name})}>
    <Text style={s.btnText}>提交申请</Text>
  </TouchableOpacity>
</View>

点击后跳转到完成页面,传递狗狗的名字用于显示。

footer: {backgroundColor: '#fff', padding: 16, paddingBottom: 32},
btn: {backgroundColor: '#D2691E', paddingVertical: 14, borderRadius: 8, alignItems: 'center'},
btnText: {fontSize: 16, fontWeight: '600', color: '#fff'},

和详情页的按钮样式一致,保持视觉统一。

表单状态管理

当前代码没有管理输入状态,实际应该这样:

const [form, setForm] = useState({
  name: '',
  phone: '',
  address: '',
  experience: '',
});

const updateField = (field: string, value: string) => {
  setForm({...form, [field]: value});
};

用一个对象存储所有字段的值。

updateField 是通用的更新函数,[field] 是计算属性名,可以动态指定要更新的字段。

<TextInput 
  style={s.input} 
  placeholder="请输入您的姓名" 
  value={form.name}
  onChangeText={(text) => updateField('name', text)}
/>

value 绑定状态值,让输入框显示当前值。

onChangeText 在用户输入时触发,更新状态。

这就是 React 的受控组件模式,状态是唯一的数据源。

表单验证

提交前要检查必填字段:

const validateForm = (): boolean => {
  if (!form.name.trim()) {
    Alert.alert('提示', '请输入姓名');
    return false;
  }
  if (!form.phone.trim()) {
    Alert.alert('提示', '请输入联系电话');
    return false;
  }
  if (!form.address.trim()) {
    Alert.alert('提示', '请输入居住地址');
    return false;
  }
  return true;
};

const handleSubmit = () => {
  if (!validateForm()) return;
  navigate('AdoptDone', {name: params.dog.name});
};

trim() 去掉首尾空格,防止用户只输入空格。

验证失败时弹窗提示,返回 false 阻止提交。

电话号码格式验证

const isValidPhone = (phone: string): boolean => {
  // 简单的手机号验证:11位数字,以1开头
  return /^1\d{10}$/.test(phone);
};

if (!isValidPhone(form.phone)) {
  Alert.alert('提示', '请输入正确的手机号码');
  return false;
}

用正则表达式验证手机号格式。

^1 以 1 开头,\d{10} 后面跟 10 位数字,$ 结尾。

这是简化的验证,实际可能需要更复杂的规则。

输入框焦点管理

填完一个字段后,自动跳到下一个:

const phoneRef = useRef<TextInput>(null);
const addressRef = useRef<TextInput>(null);

<TextInput 
  placeholder="请输入您的姓名" 
  returnKeyType="next"
  onSubmitEditing={() => phoneRef.current?.focus()}
/>
<TextInput 
  ref={phoneRef}
  placeholder="请输入联系电话" 
  returnKeyType="next"
  onSubmitEditing={() => addressRef.current?.focus()}
/>
<TextInput 
  ref={addressRef}
  placeholder="请输入居住地址" 
  returnKeyType="done"
/>

ref 获取输入框的引用。

returnKeyType 设置键盘回车键的文字,“next” 显示"下一项",“done” 显示"完成"。

onSubmitEditing 在用户按回车时触发,调用下一个输入框的 focus 方法。

这样用户可以连续填写,不用手动点击每个输入框。

键盘遮挡问题

输入框在屏幕下方时,弹出的键盘可能会遮挡它。

解决方案是用 KeyboardAvoidingView

import {KeyboardAvoidingView, Platform} from 'react-native';

<KeyboardAvoidingView 
  style={s.container} 
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
  ...
</KeyboardAvoidingView>

KeyboardAvoidingView 会在键盘弹出时自动调整布局,让输入框不被遮挡。

behavior 在 iOS 和 Android 上需要不同的值才能正常工作。

提交状态处理

防止重复提交:

const [submitting, setSubmitting] = useState(false);

const handleSubmit = async () => {
  if (submitting) return;
  if (!validateForm()) return;
  
  setSubmitting(true);
  try {
    await api.submitAdoptForm({
      dogId: params.dog.id,
      ...form,
    });
    navigate('AdoptDone', {name: params.dog.name});
  } catch (e) {
    Alert.alert('提交失败', '请稍后重试');
  } finally {
    setSubmitting(false);
  }
};

submitting 状态在提交过程中为 true,防止用户重复点击。

提交按钮也要显示加载状态:

<TouchableOpacity 
  style={[s.btn, submitting && s.btnDisabled]} 
  onPress={handleSubmit}
  disabled={submitting}
>
  <Text style={s.btnText}>{submitting ? '提交中...' : '提交申请'}</Text>
</TouchableOpacity>

页面容器样式

container: {flex: 1, backgroundColor: '#f5f5f5'},
content: {flex: 1},

灰色背景,和其他页面保持一致。

contentflex: 1 让 ScrollView 占据中间的所有空间。

小结

领养申请页面的实现要点:

  • TextInput 组件:单行输入用默认配置,多行输入加 multiline
  • 键盘类型:根据输入内容选择合适的 keyboardType
  • 表单状态:用 useState 管理所有字段的值
  • 表单验证:提交前检查必填字段和格式
  • 焦点管理:用 ref 和 onSubmitEditing 实现连续输入
  • 键盘遮挡:用 KeyboardAvoidingView 解决

表单是用户和系统交互的重要方式,好的表单设计能提升用户体验和数据质量。

下一篇讲领养完成页面,给用户一个温暖的反馈。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐