🚀【RN鸿蒙教学|第8课时】表单优化✨+AsyncStorage数据持久化💾+多终端兼容进阶

适配版本:RN 0.72.7 + OpenHarmony SDK 8.0 + @react-navigation/bottom-tabs@6.5.7 + axios@1.6.8
学习时长:90分钟⏱️
难度等级:⭐⭐⭐(进阶)

📋 目录导航 🧭

  1. 适合人群 & 课时目标
  2. 课前准备
  3. 核心知识点讲解
  4. 实操步骤(核心环节)
  5. 常见问题与解决方案
  6. 课堂小结
  7. 课后任务(必做)
  8. 核心要点总结

哈喽大家好~👋 欢迎来到React Native(RN)兼容开源鸿蒙(OpenHarmony)跨平台开发系列教学第8课时!🎓

上一课时我们搞定了基础表单开发📝,实现了TextInput输入、表单验证和Axios POST提交,让应用具备了基础的用户输入能力。这节课我们要给表单“升级”💡——优化输入体验、接入本地缓存,搞定密码输入、自定义提示、AsyncStorage数据持久化,还会解决鸿蒙多终端缓存适配的坑🕳️,让应用既好用又能“记住”用户输入!

本课时会帮你打通“表单体验+本地缓存”的全流程,为后续用户管理、数据同步打下坚实基础~


🎯 适合人群 & 课时目标

适合人群👨💻👩💻

已完成前7课时实操,掌握表单开发、TextInput输入、表单验证、Axios POST请求,想学习表单优化、数据持久化(本地缓存)的开发者

课时目标(90分钟达成🎯)

  1. ✨ 完成表单体验优化:实现密码输入、表单重置、自定义提示组件(替换原生alert),适配鸿蒙多终端;
  2. 💾 吃透RN AsyncStorage核心用法:实现表单数据本地缓存(存储/读取/删除);
  3. 🔗 实现表单与缓存联动:页面加载读缓存、提交成功更缓存、重置清空缓存;
  4. 📱 掌握鸿蒙多终端缓存适配进阶技巧,解决不同终端缓存异常;
  5. ✅ 完成功能开发、多终端测试与Git规范提交,功能完整无坑。

🔧 一、课前准备(5分钟)

提前做好这些准备,实操不卡顿👇:

  • ✅ 验证第7课时工程可正常运行:
    cd rnHarmonyDemo
    react-native run-ohos --emulator # 启动模拟器验证表单功能📱
    
  • ✅ 新建规范功能分支(避免污染旧分支🌿):
    git checkout feature-form-development
    git checkout -b feature-form-optimize-storage
    git branch # 确认当前分支🔍
    
  • ✅ 预习AsyncStorage核心API(setItem/getItem/removeItem)、自定义组件封装技巧;
  • ✅ 回顾第7课时表单状态管理、Axios POST逻辑,确认src/pages/FormPage.jssrc/api/request.js可用;
  • ✅ 打开DevEco Studio/VScode/Git Bash,确保多终端调试环境正常,RN版本0.72.7。

⚠️ 关键提醒:

  1. 优先确认表单提交功能正常,有问题先回顾第7课时排查;
  2. 提前了解AsyncStorage仅支持字符串存储,复杂数据需转JSON📝;
  3. 确认工程目录结构:src/pages/src/components/src/utils/文件夹已创建✅。

📚 二、核心知识点讲解(15分钟)

2.1 表单优化核心技巧(适配鸿蒙多终端)

基础表单体验不够?这4个优化点直接拉满✨:

优化点 具体实现 鸿蒙适配要点
🔒 密码输入 secureTextEntry={true}+密码确认框+一致性验证 符合鸿蒙隐私规范,输入掩码正常显示
🔄 表单重置 一键清空输入/错误提示/缓存 按钮尺寸适配开发板触控,点击反馈清晰
🚨 自定义提示 替换原生alert,封装Toast组件 简化动效,适配开发板性能,真机不遮挡
🎨 细节优化 输入框焦点样式、错误提示布局 开发板增大字体/间距,真机适配深色模式

2.2 AsyncStorage核心用法与鸿蒙适配(重点⭐)

AsyncStorage是RN原生轻量级本地存储方案💾,适合存表单缓存、用户偏好,核心知识点如下:

核心API(必掌握✅)
API 作用 鸿蒙适配注意
setItem(key, value) 存储数据 复杂数据(对象)需用JSON.stringify转字符串
getItem(key) 读取数据 读取后用JSON.parse解析,处理null值
removeItem(key) 删除指定缓存 key大小写敏感,确保与存储时一致
clear() 清空所有缓存 慎用!避免误删其他功能的缓存数据
鸿蒙适配核心🎯
  1. 📝 数据格式:鸿蒙解析JSON严格,存储对象必须转字符串,读取必须解析,避免语法错误;
  2. 📱 存储权限:RN已自动适配鸿蒙真机权限,无需手动申请;
  3. 🖥️ 开发板适配:存储容量有限,只存核心数据(姓名/邮箱),避免存密码,减少频繁存储;
  4. 🔍 兼容性:适配不同鸿蒙版本存储路径,防止缓存读取失败。

2.3 表单与缓存的联动逻辑

表单+缓存的核心是“数据闭环”🔄,逻辑流程图如下:

成功

失败

页面加载

AsyncStorage.getItem读取缓存

填充到表单输入框

用户编辑表单

提交表单?

AsyncStorage.setItem更新缓存

提示错误,不修改缓存

点击重置?

AsyncStorage.removeItem清空缓存

清空表单输入/错误提示

2.4 鸿蒙多终端缓存适配差异(进阶)

不同终端针对性适配,避免缓存异常👇:

终端类型 适配调整 具体操作
💻 模拟器 基础验证 测试缓存增删改查逻辑,确保联动正常
📱 鸿蒙真机 持久化+样式 验证重启应用缓存仍存在,提示组件不被键盘遮挡
🖥️ DAYU200开发板 轻量化+性能 仅缓存核心数据,简化缓存逻辑,延长超时时间

💻 三、实操步骤(50分钟,重点环节)

全程实操,一步一验证,新手也能跟得上🚶‍♂️!

3.1 步骤1:表单优化(密码输入+重置+自定义提示)(15分钟)

基于第7课时的FormPage.js,新增密码输入、重置功能,封装自定义Toast组件。

1.1 完善FormPage.js:新增密码状态与验证
// src/pages/FormPage.js 完整修改(新增密码相关逻辑)
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Dimensions } from 'react-native';
import service from '../api/request';
import CustomToast from '../components/CustomToast'; // 后续创建自定义提示组件

// 📏 鸿蒙多终端适配:开发板判断(屏幕高度<600即为开发板)
const { height } = Dimensions.get('window');
const isBoard = height < 600; // 开发板屏幕偏小,针对性适配

const FormPage = () => {
  // 🔹 原有表单状态
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  // 🔹 新增密码相关状态 🆕
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  // 🔹 错误信息状态
  const [errors, setErrors] = useState({});
  // 🔹 提交状态(防止重复提交)
  const [submitting, setSubmitting] = useState(false);
  // 🔹 自定义提示状态 🆕
  const [toastVisible, setToastVisible] = useState(false);
  const [toastType, setToastType] = useState('Success'); // Success/Error/Info
  const [toastMessage, setToastMessage] = useState('');

  // 🔹 输入回调(原有+新增密码)
  const handleNameChange = (text) => { 
    setName(text);
    // 实时清空姓名错误提示
    if (errors.name) setErrors(prev => ({ ...prev, name: '' }));
  };
  const handleEmailChange = (text) => { 
    setEmail(text);
    if (errors.email) setErrors(prev => ({ ...prev, email: '' }));
  };
  const handlePhoneChange = (text) => { 
    setPhone(text);
    if (errors.phone) setErrors(prev => ({ ...prev, phone: '' }));
  };

  // 🆕 密码输入回调+实时验证 🔒
  const handlePasswordChange = (text) => {
    setPassword(text);
    if (!text.trim()) {
      setErrors(prev => ({ ...prev, password: '密码不能为空🚫' }));
    } else if (text.length < 6) {
      setErrors(prev => ({ ...prev, password: '密码长度不能少于6位⚠️' }));
    } else {
      setErrors(prev => ({ ...prev, password: '' }));
      // 同步验证确认密码
      if (confirmPassword && confirmPassword !== text) {
        setErrors(prev => ({ ...prev, confirmPassword: '两次密码输入不一致⚠️' }));
      } else if (confirmPassword) {
        setErrors(prev => ({ ...prev, confirmPassword: '' }));
      }
    }
  };

  // 🆕 确认密码输入回调+实时验证 🔒
  const handleConfirmPasswordChange = (text) => {
    setConfirmPassword(text);
    if (!text.trim()) {
      setErrors(prev => ({ ...prev, confirmPassword: '请确认密码🚫' }));
    } else if (text !== password) {
      setErrors(prev => ({ ...prev, confirmPassword: '两次密码输入不一致⚠️' }));
    } else {
      setErrors(prev => ({ ...prev, confirmPassword: '' }));
    }
  };

  // 🔹 全局验证函数(新增密码验证)
  const validateForm = () => {
    const newErrors = {};
    // 原有姓名验证
    if (!name.trim()) newErrors.name = '姓名不能为空🚫';
    // 原有邮箱验证
    const emailReg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!email.trim()) newErrors.email = '邮箱不能为空🚫';
    else if (!emailReg.test(email.trim())) newErrors.email = '邮箱格式错误⚠️';
    // 原有电话验证
    const phoneReg = /^1[3-9]\d{9}$/;
    if (!phone.trim()) newErrors.phone = '手机号不能为空🚫';
    else if (!phoneReg.test(phone.trim())) newErrors.phone = '手机号格式错误⚠️';
    
    // 🆕 密码验证 🔒
    if (!password.trim()) {
      newErrors.password = '密码不能为空🚫';
    } else if (password.length < 6) {
      newErrors.password = '密码长度不能少于6位⚠️';
    }
    // 🆕 确认密码验证 🔒
    if (!confirmPassword.trim()) {
      newErrors.confirmPassword = '请确认密码🚫';
    } else if (confirmPassword !== password) {
      newErrors.confirmPassword = '两次密码输入不一致⚠️';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  // 🔹 重置表单函数(先空实现,后续补充缓存逻辑)🔄
  const resetForm = () => {
    setName('');
    setEmail('');
    setPhone('');
    setPassword('');
    setConfirmPassword('');
    setErrors({});
    // 显示重置提示
    setToastVisible(true);
    setToastType('Info');
    setToastMessage('表单已重置!🔄');
  };

  // 🔹 提交函数(先保留原有逻辑,后续补充缓存)📤
  const handleSubmit = async () => {
    const isValid = validateForm();
    if (!isValid) return;

    setSubmitting(true);
    try {
      const res = await service.post('/users', {
        name: name.trim(),
        email: email.trim(),
        phone: phone.trim()
      });
      // 显示成功提示(替换alert)🎉
      setToastVisible(true);
      setToastType('Success');
      setToastMessage(`✅ 表单提交成功!用户ID:${res.id || 'test_001'}`);
      // 重置表单(不重置缓存)
      setName('');
      setEmail('');
      setPhone('');
      setPassword('');
      setConfirmPassword('');
      setErrors({});
    } catch (err) {
      // 显示失败提示 ❌
      setToastVisible(true);
      setToastType('Error');
      setToastMessage(`❌ 提交失败:${err.message || '网络异常,请重试'}`);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
      <Text style={[styles.formTitle, isBoard && { fontSize: 22 }]}>新增用户表单 📝</Text>
      <View style={[styles.formContainer, isBoard && { gap: 20, paddingHorizontal: 10 }]}>
        {/* 姓名输入框 */}
        <View style={styles.inputGroup}>
          <Text style={[styles.inputLabel, isBoard && { fontSize: 18 }]}>姓名(必填)</Text>
          <TextInput
            style={[styles.input, isBoard && { height: 55, fontSize: 18 }, errors.name && styles.inputError]}
            value={name}
            onChangeText={handleNameChange}
            placeholder="请输入姓名"
            placeholderTextColor="#999"
            editable={!submitting}
          />
          {errors.name && <Text style={[styles.errorText, isBoard && { fontSize: 16 }]}>{errors.name}</Text>}
        </View>

        {/* 邮箱输入框 */}
        <View style={styles.inputGroup}>
          <Text style={[styles.inputLabel, isBoard && { fontSize: 18 }]}>邮箱(必填)</Text>
          <TextInput
            style={[styles.input, isBoard && { height: 55, fontSize: 18 }, errors.email && styles.inputError]}
            value={email}
            onChangeText={handleEmailChange}
            placeholder="请输入邮箱"
            placeholderTextColor="#999"
            keyboardType="email-address"
            editable={!submitting}
          />
          {errors.email && <Text style={[styles.errorText, isBoard && { fontSize: 16 }]}>{errors.email}</Text>}
        </View>

        {/* 电话输入框 */}
        <View style={styles.inputGroup}>
          <Text style={[styles.inputLabel, isBoard && { fontSize: 18 }]}>手机号(必填)</Text>
          <TextInput
            style={[styles.input, isBoard && { height: 55, fontSize: 18 }, errors.phone && styles.inputError]}
            value={phone}
            onChangeText={handlePhoneChange}
            placeholder="请输入手机号"
            placeholderTextColor="#999"
            keyboardType="phone-pad"
            maxLength={11}
            editable={!submitting}
          />
          {errors.phone && <Text style={[styles.errorText, isBoard && { fontSize: 16 }]}>{errors.phone}</Text>}
        </View>
        
        {/* 🆕 密码输入框 🔒 */}
        <View style={styles.inputGroup}>
          <Text style={[styles.inputLabel, isBoard && { fontSize: 18 }]}>密码(必填)</Text>
          <TextInput
            style={[styles.input, isBoard && { height: 55, fontSize: 18 }, errors.password && styles.inputError]}
            value={password}
            onChangeText={handlePasswordChange}
            placeholder="请输入密码(≥6位)"
            placeholderTextColor="#999"
            secureTextEntry={true} // 隐藏密码🔒
            maxLength={16}
            editable={!submitting}
          />
          {errors.password && <Text style={[styles.errorText, isBoard && { fontSize: 16 }]}>{errors.password}</Text>}
        </View>

        {/* 🆕 确认密码输入框 🔒 */}
        <View style={styles.inputGroup}>
          <Text style={[styles.inputLabel, isBoard && { fontSize: 18 }]}>确认密码(必填)</Text>
          <TextInput
            style={[styles.input, isBoard && { height: 55, fontSize: 18 }, errors.confirmPassword && styles.inputError]}
            value={confirmPassword}
            onChangeText={handleConfirmPasswordChange}
            placeholder="请确认密码"
            placeholderTextColor="#999"
            secureTextEntry={true}
            editable={!submitting}
          />
          {errors.confirmPassword && <Text style={[styles.errorText, isBoard && { fontSize: 16 }]}>{errors.confirmPassword}</Text>}
        </View>

        {/* 🆕 提交+重置按钮(横向布局)🖱️ */}
        <View style={[styles.btnContainer, isBoard && { gap: 15 }]}>
          <TouchableOpacity
            style={[styles.submitBtn, isBoard && { height: 55 }, submitting && styles.submitBtnDisabled]}
            onPress={handleSubmit}
            disabled={submitting}
            activeOpacity={0.8}
          >
            <Text style={[styles.submitBtnText, isBoard && { fontSize: 18 }]}>
              {submitting ? '提交中...🔄' : '提交表单 ✅'}
            </Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[styles.resetBtn, isBoard && { height: 55 }]}
            onPress={resetForm}
            disabled={submitting}
            activeOpacity={0.8}
          >
            <Text style={[styles.resetBtnText, isBoard && { fontSize: 18 }]}>重置表单 🔄</Text>
          </TouchableOpacity>
        </View>
      </View>
      {/* 🆕 自定义提示组件 🚨 */}
      <CustomToast
        visible={toastVisible}
        type={toastType}
        message={toastMessage}
        onClose={() => setToastVisible(false)}
      />
    </ScrollView>
  );
};

// 🎨 完整样式(包含所有适配)
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#f8f8f8'
  },
  formTitle: {
    fontSize: 20,
    fontWeight: '600',
    marginBottom: 20,
    textAlign: 'center',
    color: '#333'
  },
  formContainer: {
    gap: 15,
    width: '100%'
  },
  inputGroup: {
    gap: 8,
    width: '100%'
  },
  inputLabel: {
    fontSize: 16,
    color: '#333',
    fontWeight: '500'
  },
  input: {
    height: 45,
    borderWidth: 1,
    borderColor: '#eee',
    borderRadius: 8,
    paddingHorizontal: 12,
    fontSize: 16,
    backgroundColor: '#fff',
    width: '100%'
  },
  inputError: {
    borderColor: '#ff3b30' // 错误状态红色边框 ❌
  },
  errorText: {
    fontSize: 14,
    color: '#ff3b30',
    marginTop: 4
  },
  btnContainer: {
    flexDirection: 'row',
    gap: 10,
    marginTop: 10,
    width: '100%'
  },
  submitBtn: {
    flex: 1,
    height: 48,
    backgroundColor: '#007AFF',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center'
  },
  resetBtn: {
    flex: 1,
    height: 48,
    backgroundColor: '#f5f5f5',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#eee'
  },
  submitBtnDisabled: {
    backgroundColor: '#99ccff',
    opacity: 0.7
  },
  submitBtnText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '500'
  },
  resetBtnText: {
    color: '#333',
    fontSize: 16,
    fontWeight: '500'
  }
});

export default FormPage;

💡 新手提示:

  • 替换原有FormPage.js代码时,先备份旧文件(cp FormPage.js FormPage.backup.js);
  • 确保service导入路径正确(对应第7课时的request.js);
  • 开发板判断逻辑已内置,无需手动修改📱。
1.2 创建自定义提示组件(CustomToast.js)
// src/components/CustomToast.js 🚨 自定义提示组件(适配鸿蒙多终端)
import React, { useEffect } from 'react';
import { View, Text, StyleSheet, Dimensions, TouchableOpacity } from 'react-native';

const { height, width } = Dimensions.get('window');
const isBoard = height < 600; // 开发板判断

const CustomToast = ({ visible, type, message, onClose }) => {
  // 🕒 自动关闭(2秒),支持手动关闭
  useEffect(() => {
    let timer;
    if (visible) {
      timer = setTimeout(() => {
        onClose();
      }, 2000); // 2秒后自动关闭
    }
    return () => clearTimeout(timer); // 组件卸载时清除定时器,避免内存泄漏
  }, [visible, onClose]);

  if (!visible) return null;

  // 🎨 不同类型的样式(成功/失败/提示)
  const getToastStyle = () => {
    switch (type) {
      case 'Success':
        return { backgroundColor: '#4cd964', borderColor: '#34c759' }; // 绿色 ✅
      case 'Error':
        return { backgroundColor: '#ff3b30', borderColor: '#ff2d20' }; // 红色 ❌
      case 'Info':
        return { backgroundColor: '#007AFF', borderColor: '#0066cc' }; // 蓝色 ℹ️
      default:
        return { backgroundColor: '#007AFF', borderColor: '#0066cc' };
    }
  };

  // 🖱️ 支持手动点击关闭
  const handleToastPress = () => {
    onClose();
  };

  return (
    <View style={styles.toastWrapper}>
      <TouchableOpacity
        style={[
          styles.toastContainer,
          getToastStyle(),
          isBoard && { padding: 15, borderRadius: 10, minWidth: width * 0.7 }
        ]}
        onPress={handleToastPress}
        activeOpacity={0.9}
      >
        <Text style={[
          styles.toastText,
          isBoard && { fontSize: 18, fontWeight: '500', lineHeight: 24 }
        ]}>
          {message}
        </Text>
      </TouchableOpacity>
    </View>
  );
};

// 🎨 样式(适配鸿蒙多终端,无复杂动效)
const styles = StyleSheet.create({
  toastWrapper: {
    position: 'absolute',
    top: height * 0.2, // 屏幕上方20%位置,避免被键盘遮挡
    left: 0,
    right: 0,
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 9999, // 置顶显示,不被其他组件遮挡
    paddingHorizontal: 20
  },
  toastContainer: {
    padding: 12,
    borderRadius: 8,
    borderWidth: 1,
    minWidth: width * 0.6,
    justifyContent: 'center',
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.2,
    shadowRadius: 4,
    elevation: 5 // 鸿蒙真机阴影适配
  },
  toastText: {
    color: 'white',
    fontSize: 16,
    textAlign: 'center',
    lineHeight: 20
  }
});

export default CustomToast;

💡 新手提示:

  • 先创建src/components/文件夹(若不存在):mkdir -p src/components
  • 该组件支持自动关闭+手动点击关闭,适配鸿蒙开发板触控体验✅。
1.3 验证表单优化效果
# 重启Metro服务(清除缓存,避免样式缓存问题)
npx react-native start --reset-cache

# 运行工程到鸿蒙模拟器
react-native run-ohos --emulator

✅ 验证标准:

  1. 密码输入框隐藏正常,输入6位以下密码/两次密码不一致时提示错误⚠️;
  2. 点击重置按钮,所有输入内容、错误提示立即清空🔄;
  3. 提交表单后,自定义Toast提示替代原生alert,2秒自动关闭(也可手动点击关闭)✅;
  4. 开发板运行时,输入框/按钮/提示字体自动放大,触控更友好🖥️。

3.2 步骤2:集成AsyncStorage,封装存储工具类(10分钟)

封装统一的存储工具类,简化调用,处理JSON格式转换,适配鸿蒙终端。

2.1 创建存储工具类(storage.js)
// src/utils/storage.js 💾 本地存储工具类(适配鸿蒙多终端)
import AsyncStorage from '@react-native-async-storage/async-storage';

// 📏 开发板判断(复用FormPage逻辑)
const { height } = Dimensions.get('window');
const isBoard = height < 600;

/**
 * 存储数据(自动处理JSON格式)
 * @param {string} key 存储键名(大小写敏感!)
 * @param {any} value 存储值(对象/字符串/数字)
 * @returns {boolean} 是否存储成功
 */
export const setStorage = async (key, value) => {
  try {
    // 复杂数据转JSON字符串,简单数据直接存储
    const strValue = typeof value === 'object' 
      ? JSON.stringify(value) 
      : String(value);
    
    // 🖥️ 开发板延迟存储,避免卡顿
    if (isBoard) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    
    await AsyncStorage.setItem(key, strValue);
    console.log(`✅ [存储成功] Key: ${key}, Value: ${strValue.substring(0, 50)}...`); // 截断长日志
    return true;
  } catch (err) {
    console.error(`❌ [存储失败] Key: ${key}`, err.message);
    return false;
  }
};

/**
 * 读取数据(自动解析JSON格式,带容错)
 * @param {string} key 存储键名
 * @returns {any} 读取的值(对象/字符串/null)
 */
export const getStorage = async (key) => {
  try {
    const value = await AsyncStorage.getItem(key);
    if (!value) {
      console.log(`📤 [读取结果] Key: ${key} → 无数据`);
      return null;
    }
    
    // 容错解析:先尝试JSON解析,失败则返回原始字符串
    let parsedValue;
    try {
      parsedValue = JSON.parse(value);
    } catch {
      parsedValue = value;
    }
    
    console.log(`📤 [读取成功] Key: ${key} → Value: ${JSON.stringify(parsedValue).substring(0, 50)}...`);
    return parsedValue;
  } catch (err) {
    console.error(`❌ [读取失败] Key: ${key}`, err.message);
    return null;
  }
};

/**
 * 删除指定键的缓存
 * @param {string} key 存储键名
 * @returns {boolean} 是否删除成功
 */
export const removeStorage = async (key) => {
  try {
    await AsyncStorage.removeItem(key);
    console.log(`🗑️ [删除成功] Key: ${key}`);
    return true;
  } catch (err) {
    console.error(`❌ [删除失败] Key: ${key}`, err.message);
    return false;
  }
};

/**
 * 清空所有缓存(慎用!会删除所有AsyncStorage数据)
 * @returns {boolean} 是否清空成功
 */
export const clearAllStorage = async () => {
  try {
    await AsyncStorage.clear();
    console.log('🗑️ [清空成功] 所有缓存已删除');
    return true;
  } catch (err) {
    console.error('❌ [清空失败]', err.message);
    return false;
  }
};

// 补充:获取所有缓存键名(调试用)
export const getAllKeys = async () => {
  try {
    const keys = await AsyncStorage.getAllKeys();
    console.log(`🔑 [所有缓存键]`, keys);
    return keys;
  } catch (err) {
    console.error('❌ [获取键名失败]', err.message);
    return [];
  }
};

💡 新手提示:

  • 先创建src/utils/文件夹:mkdir -p src/utils
  • 存储键名大小写敏感(如formCacheFormCache),建议统一用小写+下划线📝;
  • 开发板延迟存储逻辑已内置,无需手动调整⏳。
2.2 在FormPage.js中导入存储工具类
// src/pages/FormPage.js 顶部新增导入 💾
import { setStorage, getStorage, removeStorage } from '../utils/storage';
// 补充导入Dimensions(storage.js需要)
import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Dimensions } from 'react-native';
2.3 验证存储工具类

FormPage.js中添加测试代码(组件内的useEffect):

// FormPage.js 新增useEffect测试存储功能 🧪
useEffect(() => {
  // 测试存储/读取
  const testStorage = async () => {
    // 存储测试数据
    const testSuccess = await setStorage('testKey', { name: '测试用户', age: 20, time: new Date().toLocaleString() });
    if (testSuccess) {
      // 读取测试数据
      const testData = await getStorage('testKey');
      console.log('📤 测试读取结果:', testData); // 应输出 { name: '测试用户', age: 20, time: 'xxx' }
    }
  };
  
  // 仅在开发环境测试
  if (__DEV__) {
    testStorage();
  }
}, []);

✅ 验证标准:

  • 运行工程后,控制台输出✅ [存储成功]📤 [读取成功]日志;
  • 读取结果显示完整的测试对象,无JSON解析错误⚠️。

3.3 步骤3:实现表单与缓存的联动逻辑(10分钟)

完成“加载读缓存、提交更缓存、重置清缓存”的完整闭环🔄。

3.1 页面加载时读取缓存
// FormPage.js 完善useEffect(读取表单缓存)📥
useEffect(() => {
  // 读取表单缓存
  const fetchFormCache = async () => {
    const cacheData = await getStorage('formCache');
    if (cacheData) {
      // 填充到输入框(不填充密码,保护用户隐私🔒)
      setName(cacheData.name || '');
      setEmail(cacheData.email || '');
      setPhone(cacheData.phone || '');
      console.log('📤 表单缓存加载完成:', cacheData);
    }
  };
  
  // 页面加载时执行
  fetchFormCache();
  
  // 测试用:打印所有缓存键名
  if (__DEV__) {
    getAllKeys().then(keys => console.log('🔑 当前缓存键:', keys));
  }
}, []);
3.2 提交成功时更新缓存
// FormPage.js 完善handleSubmit(提交成功更新缓存)📤
const handleSubmit = async () => {
  const isValid = validateForm();
  if (!isValid) return;

  setSubmitting(true);
  try {
    const res = await service.post('/users', {
      name: name.trim(),
      email: email.trim(),
      phone: phone.trim()
    });

    // 🆕 提交成功,存储表单缓存(不存密码,保护隐私🔒)
    const formData = {
      name: name.trim(),
      email: email.trim(),
      phone: phone.trim(),
      updateTime: new Date().toLocaleString() // 记录更新时间
    };
    const cacheSuccess = await setStorage('formCache', formData);
    if (cacheSuccess) {
      console.log('💾 表单缓存已更新');
    }

    // 显示成功提示 🎉
    setToastVisible(true);
    setToastType('Success');
    setToastMessage(`✅ 提交成功!用户ID:${res.id || 'test_001'}`);
    
    // 重置表单(不重置缓存)
    setName('');
    setEmail('');
    setPhone('');
    setPassword('');
    setConfirmPassword('');
    setErrors({});
  } catch (err) {
    // 显示失败提示 ❌
    setToastVisible(true);
    setToastType('Error');
    setToastMessage(`❌ 提交失败:${err.message || '网络异常,请重试'}`);
  } finally {
    setSubmitting(false);
  }
};
3.3 重置表单时清空缓存
// FormPage.js 完善resetForm(重置+清空缓存)🗑️
const resetForm = async () => {
  // 清空表单状态
  setName('');
  setEmail('');
  setPhone('');
  setPassword('');
  setConfirmPassword('');
  setErrors({});
  
  // 🆕 清空表单缓存
  const removeSuccess = await removeStorage('formCache');
  if (removeSuccess) {
    console.log('🗑️ 表单缓存已清空');
  }
  
  // 显示重置提示 ℹ️
  setToastVisible(true);
  setToastType('Info');
  setToastMessage('表单已重置!缓存已清空🔄');
};
3.4 验证联动逻辑

✅ 验证标准:

  1. 输入表单内容(姓名/邮箱/电话)→ 提交表单 → 关闭应用 → 重新打开,表单自动填充缓存数据📝;
  2. 点击“重置表单”→ 关闭应用 → 重新打开,表单无数据,控制台显示🗑️ 表单缓存已清空🚫;
  3. 提交表单时,控制台输出💾 表单缓存已更新,缓存包含姓名/邮箱/电话(无密码)🔒;
  4. 所有操作无报错,Toast提示正常显示✅。

3.4 步骤4:鸿蒙多终端适配进阶与测试(10分钟)

针对不同鸿蒙终端优化,确保功能稳定运行🚀。

4.1 开发板适配优化(关键!)
// FormPage.js 补充开发板样式适配 🖥️
const styles = StyleSheet.create({
  // 原有样式不变,补充/修改以下样式:
  input: {
    height: isBoard ? 60 : 45, // 开发板增大输入框高度
    borderWidth: 1,
    borderColor: '#eee',
    borderRadius: 8,
    paddingHorizontal: isBoard ? 20 : 12, // 开发板增大内边距
    fontSize: isBoard ? 20 : 16, // 开发板增大字体
    backgroundColor: '#fff',
    width: '100%'
  },
  inputLabel: {
    fontSize: isBoard ? 18 : 16, // 开发板标签字体增大
    color: '#333',
    fontWeight: '500'
  },
  errorText: {
    fontSize: isBoard ? 16 : 14, // 开发板错误提示字体增大
    color: '#ff3b30',
    marginTop: 4
  },
  btnContainer: {
    flexDirection: 'row',
    gap: isBoard ? 15 : 10, // 开发板按钮间距增大
    marginTop: isBoard ? 20 : 10,
    width: '100%'
  }
});

// storage.js 已内置开发板延迟存储逻辑,无需额外修改
4.2 多终端测试清单(必做✅)
终端类型 测试项 验证标准 注意事项
💻 鸿蒙模拟器 缓存联动+提示显示 缓存增删改查正常,Toast自动/手动关闭 测试所有表单验证规则
📱 鸿蒙真机(手机/平板) 缓存持久化+样式 重启应用缓存仍存在,提示组件不被键盘/导航栏遮挡 测试深色模式适配
🖥️ DAYU200开发板 触控+性能 输入/按钮点击流畅,缓存操作无卡顿/报错 仅缓存核心数据,不存密码

💡 开发板测试技巧:

  • 增大按钮/输入框尺寸,提升触控准确率;
  • 减少日志输出(避免开发板控制台刷屏);
  • 存储后延迟100ms再读取,避免读取到旧数据⏳。

3.5 步骤5:Git规范提交代码(5分钟)

按开源项目规范提交代码,便于后续维护和协作🤝。

# 1. 添加所有修改的文件 📁
git add .

# 2. 规范提交(符合Conventional Commits规范)📝
git commit -m "feat: 完成表单优化,集成AsyncStorage数据持久化,实现多终端适配进阶"

# 3. 推送至远程分支 🚀
git push origin feature-form-optimize-storage

# 4. 验证提交结果 🔍
git log --oneline -5 # 查看最近5次提交记录

💡 提交信息规范:

  • feat: 表示新增功能;
  • fix: 表示修复bug;
  • docs: 表示文档更新;
  • 描述简洁明了,包含核心改动点✅。

✅ 验证:

  • 远程代码仓库(如AtomGit/GitHub)可看到新提交;
  • 拉取分支代码后,工程可正常运行,功能无异常🚀。

🛠️ 四、常见问题与解决方案(10分钟,新手必看)

🚫 问题1:AsyncStorage读取对象时报JSON解析错误 ❌

现象:控制台输出SyntaxError: Unexpected token u in JSON at position 0
✅ 解决方案:

  1. 存储时确保用JSON.stringify转字符串,避免存储undefined/function等无法序列化的数据:
    // 错误示例 ❌
    await setStorage('formCache', { name: undefined });
    // 正确示例 ✅
    await setStorage('formCache', { name: name || '', email: email || '' });
    
  2. 读取时增加容错(已在storage.js中实现):
    // 容错解析逻辑
    let parsedValue;
    try {
      parsedValue = JSON.parse(value);
    } catch {
      parsedValue = value; // 解析失败返回原始字符串
    }
    

🚫 问题2:页面加载时缓存数据无法填充到表单 📥

现象:打开应用后表单为空,控制台显示📤 [读取结果] Key: formCache → 无数据
✅ 解决方案:

  1. 确认存储的key是formCache(小写),与读取时一致(大小写敏感!);
  2. 检查提交函数是否调用了setStorage,且添加await等待执行完成:
    // 错误示例 ❌(无await)
    setStorage('formCache', formData);
    // 正确示例 ✅
    await setStorage('formCache', formData);
    
  3. 开发板测试:重启开发板,重新安装应用,排查存储权限问题🔧。

🚫 问题3:自定义Toast在开发板上无法自动关闭 🕒

现象:Toast显示后一直存在,无法自动消失
✅ 解决方案:

  1. 简化定时器逻辑(已在CustomToast.js中优化):
    useEffect(() => {
      let timer;
      if (visible) {
        timer = setTimeout(() => onClose(), 2000);
      }
      return () => clearTimeout(timer); // 确保清除定时器
    }, [visible, onClose]);
    
  2. 开发板禁用复杂动画,减少性能消耗;
  3. 增加手动关闭功能(点击Toast即可关闭)🖱️。

🚫 问题4:重置表单后缓存未清空 🗑️

现象:点击重置后,关闭应用重新打开,表单仍有数据
✅ 解决方案:

  1. 确认resetForm函数是async,且调用await removeStorage('formCache')
    // 错误示例 ❌(无async/await)
    const resetForm = () => {
      removeStorage('formCache'); // 异步操作未等待
    };
    // 正确示例 ✅
    const resetForm = async () => {
      await removeStorage('formCache');
    };
    
  2. 测试时打印日志确认删除结果:
    const removeSuccess = await removeStorage('formCache');
    console.log('🗑️ 缓存删除结果:', removeSuccess); // 应输出true
    

🚫 问题5:鸿蒙真机重启后缓存丢失 💾

现象:真机重启应用后,缓存数据消失
✅ 解决方案:

  1. 确认RN版本与鸿蒙SDK兼容(0.72.7+8.0是稳定组合);
  2. 真机避免清理应用后台(鸿蒙清理后台可能删除应用缓存,属于正常现象);
  3. 重要数据可增加“手动保存”按钮,主动触发存储:
    // 手动保存缓存按钮
    <TouchableOpacity onPress={async () => {
      await setStorage('formCache', { name, email, phone });
      setToastVisible(true);
      setToastType('Info');
      setToastMessage('缓存已手动保存💾');
    }}>
      <Text>手动保存缓存 💾</Text>
    </TouchableOpacity>
    

📝 五、课堂小结(5分钟)

这节课我们完成了表单的“体验+数据”双重升级💪,核心掌握4个关键点:

  1. 🔑 表单优化核心是「体验+适配」:密码输入用secureTextEntry,自定义Toast替代alert,适配鸿蒙多终端样式;
  2. 🔑 AsyncStorage关键是「字符串存储+JSON转换」:掌握核心API,避免解析错误,保护用户隐私(不存密码);
  3. 🔑 表单与缓存联动是「加载-存储-删除」闭环:页面加载读缓存、提交成功更缓存、重置清空缓存;
  4. 🔑 鸿蒙适配是「终端差异化优化」:开发板轻量化存储+大尺寸样式,真机验证缓存持久化,模拟器测试核心逻辑。

下节课我们将学习数据更新与列表刷新🔄,实现缓存数据与首页列表的联动,让应用形成完整的数据交互闭环!


✅ 六、课后任务(必做)

📌 任务1:复盘核心功能🔄

独立完成「表单优化+AsyncStorage集成+缓存联动」全流程,熟练掌握核心逻辑,删除测试代码,整理工程结构。

📌 任务2:优化缓存功能🎯

  1. ⏰ 新增缓存有效期:存储时记录时间,读取时判断是否超过24小时,过期自动清空:
    // 存储带有效期的数据
    await setStorage('formCache', {
      name, email, phone,
      createTime: new Date().getTime() // 记录存储时间戳
    });
    
    // 读取时判断有效期
    const fetchFormCache = async () => {
      const cacheData = await getStorage('formCache');
      if (cacheData) {
        const now = new Date().getTime();
        const expireTime = 24 * 60 * 60 * 1000; // 24小时
        if (now - cacheData.createTime < expireTime) {
          // 未过期,填充数据
          setName(cacheData.name || '');
        } else {
          // 已过期,清空缓存
          await removeStorage('formCache');
        }
      }
    };
    
  2. 🗂️ 实现多组表单缓存:用不同key(如formCache_1/formCache_2)存储,避免数据覆盖;
  3. 🧹 新增“清理缓存”按钮,手动清空所有表单缓存(独立于重置功能)。

📌 任务3:优化表单体验✨

  1. 🔒 为密码输入框添加强度提示(弱/中/强):根据密码长度/字符类型判断:
    // 密码强度判断
    const getPwdStrength = (pwd) => {
      if (pwd.length < 6) return '弱';
      if (/[a-z]/.test(pwd) && /[0-9]/.test(pwd)) return '中';
      if (/[a-z]/.test(pwd) && /[0-9]/.test(pwd) && /[A-Z]/.test(pwd)) return '强';
      return '中';
    };
    
  2. 🎨 优化CustomToast:支持鸿蒙深色模式适配,根据系统主题切换样式;
  3. 🖱️ 输入框添加焦点样式,提升开发板触控体验。

📌 任务4:多终端全面测试🚀

测试项 模拟器 鸿蒙真机 DAYU200开发板
表单输入/验证
密码隐藏/强度提示
自定义Toast显示/关闭
缓存存储/读取/删除
重启应用缓存持久化

📌 任务5:预习📚

预习RN列表刷新(RefreshControl)、数据更新(useState/useContext)相关知识,为下节课做准备。


🌟 核心要点总结

  1. 表单优化:密码输入用secureTextEntry={true}实现隐藏,自定义Toast组件替代原生alert,适配鸿蒙多终端样式(开发板增大尺寸);
  2. AsyncStorage:仅支持字符串存储,复杂数据需用JSON.stringify/JSON.parse转换,存储键名大小写敏感,不存储密码等敏感数据;
  3. 缓存联动:页面加载时getStorage读取缓存、提交成功时setStorage更新缓存、重置时removeStorage清空缓存,形成完整闭环;
  4. 鸿蒙适配:开发板轻量化存储+大尺寸UI,真机验证缓存持久化,模拟器测试核心逻辑,所有操作添加异常处理和日志输出。

如果实操中遇到表单优化、AsyncStorage、鸿蒙适配相关问题,欢迎留言💬!下节课我们解锁数据更新与列表刷新,让应用功能更完善🚀~

关注我,后续课时持续更新,从0到1掌握RN兼容鸿蒙开发,每一步都踩实🌟!

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐