在React Native中开发一个用于商城App的表单验证组件,你可以使用多种方法来实现。这里,我将介绍一种常见的方法,使用Formik库来处理表单状态和验证。Formik是一个非常流行的React库,它可以帮助你构建表单,并轻松地处理表单状态和验证。

步骤1:安装Formik

首先,你需要在你的React Native项目中安装FormikYup(一个JavaScript的验证库)。

npm install formik yup

或者使用yarn:

yarn add formik yup

步骤2:创建表单验证组件

接下来,你可以创建一个表单验证组件。例如,我们创建一个简单的用户注册表单。

import React from 'react';
import { View, TextInput, Button, Text } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';

const SignupSchema = Yup.object().shape({
  firstName: Yup.string()
    .min(2, 'Too Short!')
    .max(50, 'Too Long!')
    .required('Required'),
  lastName: Yup.string()
    .min(2, 'Too Short!')
    .max(50, 'Too Long!')
    .required('Required'),
  email: Yup.string()
    .email('Invalid email')
    .required('Required'),
  password: Yup.string()
    .min(6, 'Too Short!')
    .required('Required'),
});

const SignupForm = () => (
  <Formik
    initialValues={{ firstName: '', lastName: '', email: '', password: '' }}
    onSubmit={values => {
      // handle form submit (e.g., send to server)
      console.log(values);
    }}
    validationSchema={SignupSchema}
  >
    {({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (
      <View>
        <TextInput
          onChangeText={handleChange('firstName')}
          onBlur={handleBlur('firstName')}
          value={values.firstName}
          placeholder="First Name"
        />
        {errors.firstName && touched.firstName ? <Text>{errors.firstName}</Text> : null}
        <TextInput
          onChangeText={handleChange('lastName')}
          onBlur={handleBlur('lastName')}
          value={values.lastName}
          placeholder="Last Name"
        />
        {errors.lastName && touched.lastName ? <Text>{errors.lastName}</Text> : null}
        <TextInput
          onChangeText={handleChange('email')}
          onBlur={handleBlur('email')}
          value={values.email}
          placeholder="Email"
          keyboardType="email-address"
        />
        {errors.email && touched.email ? <Text>{errors.email}</Text> : null}
        <TextInput
          onChangeText={handleChange('password')}
          onBlur={handleBlur('password')}
          value={values.password}
          placeholder="Password"
          secureTextEntry={true}
        />
        {errors.password && touched.password ? <Text>{errors.password}</Text> : null}
        <Button onPress={handleSubmit} title="Submit" />
      </View>
    )}
  </Formik>
);

步骤3:使用表单组件

在你的应用中使用这个SignupForm组件。例如,在一个屏幕组件中:

import React from 'react';
import { SafeAreaView } from 'react-native';
import SignupForm from './SignupForm'; // 确保路径正确

const SignupScreen = () => {
  return (
    <SafeAreaView>
      <SignupForm />
    </SafeAreaView>
  );
};

这样,你就创建了一个带有基本验证功能的表单。用户提交表单时,如果输入不满足条件,将显示相应的错误信息。如果输入有效,你可以在onSubmit函数中处理提交逻辑,例如发送数据到服务器。 这种方法使得表单管理和验证变得简单且强大。


// app.tsx
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView, Image, Alert } from 'react-native';

const App = () => {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    phone: '',
    password: '',
    confirmPassword: ''
  });
  
  const [errors, setErrors] = useState<Record<string, string>>({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  // Base64 icons
  const icons = {
    user: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjAgMjF2LTFhNCA0IDAgMCAwLTQtNEgxMGE0IDQgMCAwIDAtNCA0djFhMSAxIDAgMCAwIDEgMWgxNGExIDEgMCAwIDAgMS0xek05IDEwaDRhNCA0IDAgMSAxIDAgOGgtNGE0IDQgMCAxIDEgMCAtOHoiPjwvcGF0aD48L3N2Zz4=',
    email: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB4PSIyIiB5PSI0IiB3aWR0aD0iMjAiIGhlaWdodD0iMTYiIHJ4PSIyIj48L3JlY3Q+PHBhdGggZD0iTTEyIDE5LjVMMiAxM3YtOWwyMCA3WiI+PC9wYXRoPjwvc3ZnPg==',
    phone: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTcgMkEzIDMgMCAwIDAgMTQgNWMyIDQgMyA5IDMgOWExIDEgMCAwIDAgMSAxaDJhMSAxIDAgMCAwIDEtMWMwLTctMS0xMi0zLTE2eiI+PC9wYXRoPjxwYXRoIGQ9Ik05LjUgMTJhMSAxIDAgMSAwIDAtMm01IDBhMSAxIDAgMSAwIDAtMm0tNSA1YTEgMSAwIDEgMCAwLTJtNSAwYTEgMSAwIDEgMCAwLTJtLTUgNWEyIDIgMCAxIDAgMC00bTUgMGEyIDIgMCAxIDAgMC00Ij48L3BhdGg+PC9zdmc+',
    lock: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB4PSIzIiB5PSIxMSIgd2lkdGg9IjE4IiBoZWlnaHQ9IjExIiByeD0iMiIgcnk9IjIiPjwvcmVjdD48cGF0aCBkPSJNNyAxMVY3YTIgMiAwIDAgMSAyLTJoNGEyIDIgMCAwIDEgMiAydjQiPjwvcGF0aD48L3N2Zz4=',
    check: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cG9seWxpbmUgcG9pbnRzPSIyMCA2IDkgMTcgNCAxMiI+PC9wb2x5bGluZT48L3N2Zz4=',
    error: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNFRTQ0M0EiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PGxpbmUgeDE9IjE1IiB5MT0iOSIgeDI9IjkiIHkyPSIxNSI+PC9saW5lPjxsaW5lIHgxPSI5IiB5MT0iOSIgeDI9IjE1IiB5Mj0iMTUiPjwvbGluZT48L3N2Zz4='
  };

  const validateField = (name: string, value: string) => {
    switch (name) {
      case 'username':
        if (!value) return '用户名不能为空';
        if (value.length < 3) return '用户名至少3个字符';
        if (value.length > 20) return '用户名不能超过20个字符';
        return '';
      
      case 'email':
        if (!value) return '邮箱不能为空';
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(value)) return '请输入有效的邮箱地址';
        return '';
      
      case 'phone':
        if (!value) return '手机号不能为空';
        const phoneRegex = /^1[3-9]\d{9}$/;
        if (!phoneRegex.test(value)) return '请输入有效的手机号';
        return '';
      
      case 'password':
        if (!value) return '密码不能为空';
        if (value.length < 6) return '密码至少6个字符';
        return '';
      
      case 'confirmPassword':
        if (!value) return '请确认密码';
        if (value !== formData.password) return '两次输入的密码不一致';
        return '';
      
      default:
        return '';
    }
  };

  const handleChange = (name: string, value: string) => {
    setFormData(prev => ({ ...prev, [name]: value }));
    
    // 实时验证
    const error = validateField(name, value);
    setErrors(prev => ({ ...prev, [name]: error }));
  };

  const validateForm = () => {
    const newErrors: Record<string, string> = {};
    let isValid = true;
    
    Object.keys(formData).forEach(key => {
      const error = validateField(key, formData[key as keyof typeof formData]);
      if (error) {
        newErrors[key] = error;
        isValid = false;
      }
    });
    
    setErrors(newErrors);
    return isValid;
  };

  const handleSubmit = () => {
    if (validateForm()) {
      setIsSubmitting(true);
      // 模拟提交过程
      setTimeout(() => {
        setIsSubmitting(false);
        Alert.alert('注册成功', '您的账户已经创建成功!');
        setFormData({
          username: '',
          email: '',
          phone: '',
          password: '',
          confirmPassword: ''
        });
      }, 1500);
    } else {
      Alert.alert('验证失败', '请检查并修正表单中的错误');
    }
  };

  const getInputBorderColor = (fieldName: string) => {
    if (errors[fieldName]) return '#EE443A'; // 错误颜色
    if (formData[fieldName as keyof typeof formData]) return '#4285F4'; // 成功颜色
    return '#ddd'; // 默认颜色
  };

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>商城注册</Text>
        <Text style={styles.subtitle}>请填写您的个人信息</Text>
      </View>

      <View style={styles.formContainer}>
        {/* 用户名输入 */}
        <View style={styles.inputGroup}>
          <View style={styles.inputLabel}>
            <Image source={{ uri: icons.user }} style={styles.inputIcon} />
            <Text style={styles.labelText}>用户名</Text>
          </View>
          <TextInput
            style={[styles.input, { borderColor: getInputBorderColor('username') }]}
            value={formData.username}
            onChangeText={(value) => handleChange('username', value)}
            placeholder="请输入用户名"
          />
          {errors.username ? (
            <View style={styles.errorContainer}>
              <Image source={{ uri: icons.error }} style={styles.errorIcon} />
              <Text style={styles.errorText}>{errors.username}</Text>
            </View>
          ) : formData.username && !errors.username ? (
            <View style={styles.successContainer}>
              <Image source={{ uri: icons.check }} style={styles.successIcon} />
              <Text style={styles.successText}>用户名可用</Text>
            </View>
          ) : null}
        </View>

        {/* 邮箱输入 */}
        <View style={styles.inputGroup}>
          <View style={styles.inputLabel}>
            <Image source={{ uri: icons.email }} style={styles.inputIcon} />
            <Text style={styles.labelText}>邮箱</Text>
          </View>
          <TextInput
            style={[styles.input, { borderColor: getInputBorderColor('email') }]}
            value={formData.email}
            onChangeText={(value) => handleChange('email', value)}
            placeholder="请输入邮箱地址"
            keyboardType="email-address"
          />
          {errors.email ? (
            <View style={styles.errorContainer}>
              <Image source={{ uri: icons.error }} style={styles.errorIcon} />
              <Text style={styles.errorText}>{errors.email}</Text>
            </View>
          ) : formData.email && !errors.email ? (
            <View style={styles.successContainer}>
              <Image source={{ uri: icons.check }} style={styles.successIcon} />
              <Text style={styles.successText}>邮箱格式正确</Text>
            </View>
          ) : null}
        </View>

        {/* 手机号输入 */}
        <View style={styles.inputGroup}>
          <View style={styles.inputLabel}>
            <Image source={{ uri: icons.phone }} style={styles.inputIcon} />
            <Text style={styles.labelText}>手机号</Text>
          </View>
          <TextInput
            style={[styles.input, { borderColor: getInputBorderColor('phone') }]}
            value={formData.phone}
            onChangeText={(value) => handleChange('phone', value)}
            placeholder="请输入手机号"
            keyboardType="phone-pad"
          />
          {errors.phone ? (
            <View style={styles.errorContainer}>
              <Image source={{ uri: icons.error }} style={styles.errorIcon} />
              <Text style={styles.errorText}>{errors.phone}</Text>
            </View>
          ) : formData.phone && !errors.phone ? (
            <View style={styles.successContainer}>
              <Image source={{ uri: icons.check }} style={styles.successIcon} />
              <Text style={styles.successText}>手机号有效</Text>
            </View>
          ) : null}
        </View>

        {/* 密码输入 */}
        <View style={styles.inputGroup}>
          <View style={styles.inputLabel}>
            <Image source={{ uri: icons.lock }} style={styles.inputIcon} />
            <Text style={styles.labelText}>密码</Text>
          </View>
          <TextInput
            style={[styles.input, { borderColor: getInputBorderColor('password') }]}
            value={formData.password}
            onChangeText={(value) => handleChange('password', value)}
            placeholder="请输入密码"
            secureTextEntry
          />
          {errors.password ? (
            <View style={styles.errorContainer}>
              <Image source={{ uri: icons.error }} style={styles.errorIcon} />
              <Text style={styles.errorText}>{errors.password}</Text>
            </View>
          ) : formData.password && !errors.password ? (
            <View style={styles.successContainer}>
              <Image source={{ uri: icons.check }} style={styles.successIcon} />
              <Text style={styles.successText}>密码强度合格</Text>
            </View>
          ) : null}
        </View>

        {/* 确认密码输入 */}
        <View style={styles.inputGroup}>
          <View style={styles.inputLabel}>
            <Image source={{ uri: icons.lock }} style={styles.inputIcon} />
            <Text style={styles.labelText}>确认密码</Text>
          </View>
          <TextInput
            style={[styles.input, { borderColor: getInputBorderColor('confirmPassword') }]}
            value={formData.confirmPassword}
            onChangeText={(value) => handleChange('confirmPassword', value)}
            placeholder="请再次输入密码"
            secureTextEntry
          />
          {errors.confirmPassword ? (
            <View style={styles.errorContainer}>
              <Image source={{ uri: icons.error }} style={styles.errorIcon} />
              <Text style={styles.errorText}>{errors.confirmPassword}</Text>
            </View>
          ) : formData.confirmPassword && !errors.confirmPassword ? (
            <View style={styles.successContainer}>
              <Image source={{ uri: icons.check }} style={styles.successIcon} />
              <Text style={styles.successText}>密码匹配</Text>
            </View>
          ) : null}
        </View>

        {/* 提交按钮 */}
        <TouchableOpacity 
          style={[styles.submitButton, isSubmitting && styles.submittingButton]} 
          onPress={handleSubmit}
          disabled={isSubmitting}
        >
          <Text style={styles.submitButtonText}>
            {isSubmitting ? '注册中...' : '立即注册'}
          </Text>
        </TouchableOpacity>
      </View>

      <View style={styles.footer}>
        <Text style={styles.footerText}>已有账号?登录</Text>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8f9fa',
    padding: 20
  },
  header: {
    alignItems: 'center',
    marginBottom: 30,
    paddingTop: 30
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#2c3e50'
  },
  subtitle: {
    fontSize: 16,
    color: '#7f8c8d',
    marginTop: 8
  },
  formContainer: {
    backgroundColor: '#fff',
    borderRadius: 16,
    padding: 24,
    elevation: 4,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8
  },
  inputGroup: {
    marginBottom: 20
  },
  inputLabel: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 8
  },
  inputIcon: {
    width: 20,
    height: 20,
    marginRight: 10
  },
  labelText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#34495e'
  },
  input: {
    borderWidth: 2,
    borderRadius: 12,
    padding: 14,
    fontSize: 16,
    backgroundColor: '#fafbfc'
  },
  errorContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: 8
  },
  errorIcon: {
    width: 16,
    height: 16,
    marginRight: 6
  },
  errorText: {
    color: '#EE443A',
    fontSize: 14
  },
  successContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: 8
  },
  successIcon: {
    width: 16,
    height: 16,
    marginRight: 6
  },
  successText: {
    color: '#4285F4',
    fontSize: 14
  },
  submitButton: {
    backgroundColor: '#4285F4',
    borderRadius: 12,
    padding: 16,
    alignItems: 'center',
    marginTop: 10
  },
  submittingButton: {
    backgroundColor: '#a0c4f7'
  },
  submitButtonText: {
    color: '#fff',
    fontSize: 18,
    fontWeight: '600'
  },
  footer: {
    alignItems: 'center',
    marginTop: 20,
    marginBottom: 30
  },
  footerText: {
    color: '#4285F4',
    fontSize: 16,
    fontWeight: '600'
  }
});

export default App;

这段React Native表单验证代码实现了一个完整的用户注册功能,其核心原理基于React的状态管理和表单处理机制。从鸿蒙系统适配的角度深入分析,该代码体现了现代跨平台移动应用开发的核心理念。

代码通过useState钩子管理三个核心状态:formData(表单数据)、errors(错误信息)和isSubmitting(提交状态)。这种状态管理模式与鸿蒙系统的分布式数据管理理念高度契合,鸿蒙系统强调数据驱动的UI更新机制,而React的单向数据流正好满足这一需求。当用户在输入框中输入数据时,通过onChangeText回调函数实时更新组件状态,触发界面重新渲染,这种响应式更新机制在鸿蒙设备上能够提供流畅的用户体验。

validateField函数实现了针对不同字段的验证逻辑,通过switch语句对用户名、邮箱、手机号、密码等字段进行不同的正则表达式验证。这种验证机制在鸿蒙系统中具有重要意义,因为鸿蒙设备涵盖了手机、平板、智能手表等多种终端设备,用户输入行为在不同设备上可能存在差异,严格的前端验证能够确保数据的一致性。特别是手机号验证使用了/^1[3-9]\d{9}$/正则表达式,这符合中国大陆手机号码的格式规范,在鸿蒙系统的中国市场适配中具有实际价值。

handleChange函数作为输入处理的核心,不仅更新表单数据,还实时调用验证函数并更新错误状态。这种实时验证机制在鸿蒙系统的输入法适配中发挥重要作用,鸿蒙系统提供了智能输入法和预测输入功能,实时验证能够与这些系统特性良好配合,为用户提供即时反馈。

请添加图片描述

validateForm函数实现了表单的整体验证逻辑,通过遍历formData对象的每个字段进行验证,这种设计确保了表单提交前的完整性检查。在鸿蒙系统的应用开发中,这种全面的验证机制能够有效减少网络请求失败的概率,提升应用性能和用户体验。

handleSubmit函数模拟了表单提交过程,通过setTimeout模拟异步请求,在鸿蒙系统的网络环境适配中,这种模拟机制能够帮助开发者测试不同网络条件下的应用表现。setIsSubmitting状态的使用体现了良好的用户交互设计,避免用户重复提交表单。

getInputBorderColor函数实现了输入框边框颜色的动态变化,根据验证结果显示不同颜色反馈。这种视觉反馈机制在鸿蒙系统的UI设计中非常重要,鸿蒙系统强调直观的用户界面反馈,通过颜色变化用户能够快速了解输入状态。

UI布局采用ScrollView作为根容器,确保内容在小屏幕设备上的可滚动性,这在鸿蒙系统的多设备适配中至关重要。View组件作为布局容器,通过flex布局实现响应式设计,能够自适应不同尺寸的鸿蒙设备屏幕。

TextInput组件通过keyboardType属性指定键盘类型,如email-address,这种细节优化在鸿蒙系统中能够调用相应的系统输入法,提供更便捷的输入体验。secureTextEntry属性用于密码输入框,确保用户隐私安全,这符合鸿蒙系统对应用安全性的严格要求。

图像资源通过Image组件的uri属性加载,这种远程图片加载方式在鸿蒙系统的网络图片缓存机制中能够获得良好的性能表现。错误和成功状态的图标反馈体现了精细化的用户体验设计。


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐