🚀【RN鸿蒙教学|第9课时】数据更新+列表刷新实战:缓存与列表联动+多终端兼容闭环🎯

适配版本: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)跨平台开发系列教学第9课时!

上一课时我们完成了表单优化与AsyncStorage数据持久化💾,实现了表单的完整输入、验证、缓存功能,让应用具备了本地数据存储能力。本节课我们将聚焦数据更新与列表刷新两大核心,搞定编辑表单开发、Axios PUT请求实现数据更新、RN列表组件优化与刷新功能,关键实现“本地缓存→数据更新→列表同步刷新”的完整联动🔄,同时解决鸿蒙多终端列表适配与数据同步异常问题,让应用形成完整的数据交互闭环,进一步提升应用实用性与流畅度🚀。

本课时核心目标是帮大家熟练掌握RN数据更新(PUT请求)与列表刷新的核心技巧,实现本地缓存与首页列表的联动同步,掌握鸿蒙多终端列表适配进阶方法,巩固组件复用、网络请求、状态管理和Git代码提交规范,解决数据联动与列表刷新的常见问题,夯实跨平台开发核心能力💪。


🎯 适合人群 & 课时目标

适合人群👨💻👩💻

已完成前8课时实操,掌握表单优化、AsyncStorage数据持久化、Axios POST请求、基础列表展示,想要学习数据更新、列表刷新与数据联动的开发者

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

  1. ✨ 完成编辑表单开发,复用原有表单组件,实现编辑时数据回显、验证与提交,适配鸿蒙多终端;
  2. 📤 熟练掌握Axios PUT请求用法,实现表单数据更新,联动AsyncStorage缓存同步更新;
  3. 📜 优化RN列表组件,实现列表下拉刷新、上拉加载(简易版),同步缓存与接口数据;
  4. 🔗 实现缓存、数据更新、列表刷新的完整联动,确保数据一致性,适配鸿蒙多终端;
  5. ✅ 解决数据更新、列表刷新、多终端适配的常见问题,完成功能开发、测试与Git规范提交。

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

提前做好以下准备,确保课时内高效实操,无缝衔接上一课时内容👇:

  • ✅ 确认第8课时完成的RN鸿蒙工程(rnHarmonyDemo)可正常运行,表单优化、AsyncStorage缓存功能无异常,首页列表(用户列表)可正常展示;
  • ✅ 新建规范功能分支(避免污染旧分支🌿):
    git checkout feature-form-optimize-storage
    git checkout -b feature-data-update-list-refresh
    git branch # 确认当前分支🔍
    
  • ✅ 预习RN列表组件(FlatList)核心属性、Axios PUT请求用法、列表下拉刷新/上拉加载实现技巧,了解鸿蒙终端列表适配要点;
  • ✅ 回顾第7、8课时的表单开发、AsyncStorage缓存、Axios POST请求逻辑,确认FormPage.jsstorage.jsrequest.js可正常使用;
  • ✅ 打开DevEco Studio、VScode(加载RN工程)、Git Bash,确认所有工具可正常使用,多终端调试环境正常,RN版本保持0.72.7;
  • ✅ 准备测试接口:https://jsonplaceholder.typicode.com/users(支持GET/POST/PUT/DELETE,免费测试用🆓)。

⚠️ 关键提醒:

  1. 重点确认AsyncStorage缓存(formCache)、表单提交功能、首页列表展示正常;若列表无法展示、缓存读取失败,先回顾前序课时排查;
  2. 测试接口无需鉴权,可直接调用,优先验证GET/PUT请求是否能正常发起。

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

2.1 数据更新核心逻辑(PUT请求+缓存联动)

数据更新对应“编辑用户信息”场景,核心逻辑是**“读取数据→回显表单→验证输入→更新接口→同步缓存”**,核心要点如下:

核心环节 实现要点 鸿蒙适配注意
📤 Axios PUT请求 请求方式为PUT,地址携带数据ID(如/users/1),请求体携带完整更新数据 适配真机网络波动🌐,添加超时处理
♻️ 表单复用 复用第8课时的FormPage组件,通过isEdit状态切换“新增/编辑”模式 开发板保持表单样式一致性🎨,避免模式切换导致布局错乱
🔄 数据回显 编辑时从缓存/接口读取数据,自动填充到输入框 确保数据字段匹配✅,避免鸿蒙解析JSON时字段缺失
💾 缓存同步 更新成功后立即覆盖本地缓存,确保缓存与接口数据一致 开发板延迟存储⏳,避免卡顿

2.2 RN列表组件优化与刷新功能(重点⭐)

RN原生FlatList是列表展示的核心,本节课重点优化体验,实现下拉刷新、上拉加载,核心属性/功能如下:

FlatList核心优化属性
属性 作用 鸿蒙适配技巧
refreshing 控制下拉刷新状态(true=刷新中) 开发板简化刷新动画🎡,避免性能消耗
onRefresh 下拉刷新触发的回调函数 防抖处理🛡️,避免频繁触发接口请求
onEndReached 上拉加载触发的回调函数 开发板增大触发距离📏,提升触控体验
keyExtractor 为列表项设置唯一key 必须使用数据ID,避免RN渲染警告⚠️
renderItem 渲染单个列表项 开发板增大列表项尺寸/字体📝,提升触控
列表与缓存联动逻辑

页面加载

优先读取本地缓存列表💾

展示缓存数据(提升速度⚡)

发起接口请求获取最新数据🌐

更新本地缓存+列表数据🔄

下拉刷新🔽

重新执行B-E流程

数据更新成功✅

同步更新缓存+触发列表刷新

2.3 鸿蒙多终端适配进阶(列表+数据同步)

不同终端针对性优化,避免列表卡顿、数据同步异常:

终端类型 适配重点 具体操作
💻 模拟器 逻辑验证 测试数据更新、列表刷新、缓存联动的逻辑正确性
📱 鸿蒙真机(手机/平板) 布局+网络 平板端双列展示列表📱,手机端单列;延长请求超时时间
🖥️ DAYU200开发板 性能+触控 简化列表样式/动效🎨;增大列表项尺寸;减少渲染数据量(5-8条)

2.4 数据一致性保障技巧

确保缓存、接口、列表数据一致,避免错乱:

  1. 🎯 单一数据源:以接口数据为最终标准,缓存仅作为“加速层”;
  2. 🕒 缓存同步时机:新增/更新/删除数据后,立即同步更新缓存;
  3. ❗ 异常处理:请求失败时恢复缓存数据,显示明确提示;
  4. ✅ 数据校验:渲染/提交前校验字段完整性,避免无效数据。

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

3.1 步骤1:开发编辑表单,实现数据回显(15分钟)

复用第8课时的FormPage.js,新增“编辑模式”,实现数据回显,适配鸿蒙多终端。

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 PropTypes from 'prop-types'; // 新增:导入PropTypes
import service from '../api/request';
import CustomToast from '../components/CustomToast';
import { setStorage, getStorage, removeStorage } from '../utils/storage';

// 📏 鸿蒙多终端适配:开发板/平板判断
const { height, width } = Dimensions.get('window');
const isBoard = height < 600; // 开发板
const isTablet = width > 768; // 平板

const FormPage = ({ isEdit = false, editData = null }) => {
  // 🔹 表单状态(原有)
  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');
  const [toastMessage, setToastMessage] = useState('');

  // 🔹 数据回显逻辑(核心!)
  useEffect(() => {
    const fetchData = async () => {
      if (isEdit) {
        // 编辑模式:优先使用外部传入的editData,否则读取缓存
        const data = editData || await getStorage('formCache');
        if (data) {
          setName(data.name || '');
          setEmail(data.email || '');
          setPhone(data.phone || '');
          // 密码不回显(保护隐私🔒)
          setPassword('');
          setConfirmPassword('');
          console.log('📤 编辑数据回显成功:', data);
        }
      } else {
        // 新增模式:读取缓存填充(原有逻辑)
        const cacheData = await getStorage('formCache');
        if (cacheData) {
          setName(cacheData.name || '');
          setEmail(cacheData.email || '');
          setPhone(cacheData.phone || '');
        }
      }
    };
    fetchData();
  }, [isEdit, editData]);

  // 🔹 输入回调(原有,无修改)
  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 (!isEdit) {
      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;
  };

  // 🔹 重置表单(新增clearCache参数)
  const resetForm = async (clearCache = true) => {
    setName('');
    setEmail('');
    setPhone('');
    setPassword('');
    setConfirmPassword('');
    setErrors({});
    // 根据参数决定是否清空缓存
    if (clearCache) await removeStorage('formCache');
    // 显示提示
    setToastVisible(true);
    setToastType('Info');
    setToastMessage(`表单已重置!${clearCache ? '缓存已清空🔄' : ''}`);
  };

  // 🔹 提交/更新函数(核心:POST/PUT切换)
  const handleSubmit = async () => {
    const isValid = validateForm();
    if (!isValid) return;

    setSubmitting(true);
    try {
      const formData = { 
        name: name.trim(), 
        email: email.trim(), 
        phone: phone.trim() 
      };
      let res;

      if (isEdit) {
        // 🆕 编辑模式:PUT请求(更新数据)
        const userId = editData?.id || 1; // 优先用传入的ID,默认1
        res = await service.put(`/users/${userId}`, formData);
        console.log('✅ PUT请求成功:', res.data);
      } else {
        // 新增模式:POST请求(原有逻辑)
        res = await service.post('/users', formData);
        console.log('✅ POST请求成功:', res.data);
      }

      // 🆕 同步更新本地缓存
      await setStorage('formCache', formData);
      // 同步更新列表缓存(关键!确保列表刷新能拿到最新数据)
      await setStorage('listCache', []); // 清空列表缓存,触发重新加载

      // 显示提示
      setToastVisible(true);
      setToastType('Success');
      setToastMessage(isEdit ? '✅ 数据更新成功!' : '✅ 表单提交成功!');
      // 重置表单(编辑模式不清空缓存)
      resetForm(!isEdit);

    } catch (err) {
      console.error('❌ 请求失败:', err);
      setToastVisible(true);
      setToastType('Error');
      setToastMessage(`操作失败:${err.message || '网络异常,请重试'}`);
    } finally {
      setSubmitting(false);
    }
  };

  // 🔹 渲染UI(适配编辑模式)
  return (
    <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
      {/* 动态标题:新增/编辑 */}
      <Text style={[styles.formTitle, isBoard && { fontSize: 22 }]}>
        {isEdit ? '编辑用户表单 ✏️' : '新增用户表单 📝'}
      </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>

        {/* 密码输入框:编辑模式隐藏 */}
        {!isEdit && (
          <>
            <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 ? '提交中...🔄' : (isEdit ? '更新数据 ✏️' : '提交表单 ✅')}
            </Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[styles.resetBtn, isBoard && { height: 55 }]}
            onPress={() => resetForm(!isEdit)}
            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>
  );
};

// 🆕 添加PropTypes校验(规范外部传参)
FormPage.propTypes = {
  isEdit: PropTypes.bool,
  editData: PropTypes.object
};

// 🆕 默认参数
FormPage.defaultProps = {
  isEdit: false,
  editData: null
};

// 🎨 样式(原有+适配)
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: isBoard ? 60 : 45,
    borderWidth: 1,
    borderColor: '#eee',
    borderRadius: 8,
    paddingHorizontal: isBoard ? 20 : 12,
    fontSize: isBoard ? 20 : 16,
    backgroundColor: '#fff',
    width: '100%'
  },
  inputError: {
    borderColor: '#ff3b30'
  },
  errorText: {
    fontSize: isBoard ? 16 : 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;
1.2 验证编辑表单与数据回显
# 重启Metro服务(清除缓存)
npx react-native start --reset-cache
# 运行工程
react-native run-ohos --emulator

💡 测试技巧:手动修改isEdittrue,测试数据回显:

// 临时测试:在FormPage组件内添加
useEffect(() => {
  // 模拟传入编辑数据
  setEditData({ id: 1, name: '测试用户', email: 'test@demo.com', phone: '13800138000' });
}, []);

✅ 验证标准:

  1. 编辑模式下,表单标题变为“编辑用户表单 ✏️”,提交按钮变为“更新数据 ✏️”;
  2. 编辑模式隐藏密码输入框(保护隐私🔒);
  3. 数据自动回显到姓名/邮箱/电话输入框,输入框可正常编辑;
  4. 开发板/真机上表单布局正常,触控无异常👆。

3.2 步骤2:实现Axios PUT请求,同步缓存更新(10分钟)

确保request.js支持PUT请求(Axios默认支持,只需确认配置正确),并验证PUT请求与缓存同步。

2.1 确认request.js配置(无需修改)
// src/api/request.js(原有配置,确保baseURL正确)
import axios from 'axios';

// 创建Axios实例
const service = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com', // 测试接口地址
  timeout: 10000, // 超时时间(适配真机网络波动,延长至10秒)
  headers: {
    'Content-Type': 'application/json'
  }
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    console.log(`📤 ${config.method.toUpperCase()}请求:${config.url}`);
    return config;
  },
  (error) => {
    console.error('❌ 请求拦截器错误:', error);
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    console.log(`📥 响应成功:`, response.data);
    return response;
  },
  (error) => {
    console.error('❌ 响应错误:', error.message);
    return Promise.reject(error);
  }
);

export default service;
2.2 验证PUT请求与缓存同步

✅ 验证标准:

  1. 编辑模式下点击“更新数据 ✏️”,控制台输出📤 PUT请求:/users/1✅ PUT请求成功
  2. 更新成功后,控制台输出✅ [存储成功] Key: formCache(缓存同步更新💾);
  3. 关闭应用重新打开,表单回显更新后的数据;
  4. 新增模式仍正常使用POST请求,无冲突🚫。

3.3 步骤3:优化FlatList列表,实现下拉刷新(15分钟)

创建/完善HomePage.js,实现列表展示、下拉刷新,联动缓存与接口数据。

3.1 完整HomePage.js代码
// src/pages/HomePage.js 📜 首页列表组件(带下拉刷新+多终端适配)
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, Dimensions, RefreshControl, TouchableOpacity } from 'react-native';
import { getStorage, setStorage } from '../utils/storage';
import service from '../api/request';
import FormPage from './FormPage'; // 导入表单组件(用于跳转编辑,简化版)

// 📏 鸿蒙多终端适配
const { height, width } = Dimensions.get('window');
const isBoard = height < 600; // 开发板
const isTablet = width > 768; // 平板
const ITEM_WIDTH = isTablet ? (width - 40) / 2 : width - 20; // 平板双列布局

const HomePage = () => {
  // 🔹 列表状态
  const [listData, setListData] = useState([]);
  const [refreshing, setRefreshing] = useState(false);
  const [loading, setLoading] = useState(false);
  const [selectedItem, setSelectedItem] = useState(null); // 选中的编辑项
  const [showForm, setShowForm] = useState(false); // 是否显示表单

  // 🔹 加载列表数据(核心:缓存+接口联动)
  const loadListData = async () => {
    setRefreshing(true);
    try {
      // 1. 优先读取本地缓存(提升加载速度⚡)
      const cacheList = await getStorage('listCache') || [];
      if (cacheList.length > 0) {
        setListData(cacheList);
        console.log('📜 加载缓存列表:', cacheList.length, '条');
      }

      // 2. 发起接口请求,同步最新数据
      const res = await service.get('/users');
      const apiList = res.data.slice(0, isBoard ? 8 : 10); // 开发板只加载8条,避免卡顿
      
      // 3. 更新本地缓存+列表数据
      await setStorage('listCache', apiList);
      setListData(apiList);
      console.log('📜 加载接口列表:', apiList.length, '条');

    } catch (err) {
      console.error('❌ 列表加载失败:', err.message);
      // 接口失败时,使用缓存数据兜底🛡️
      if (listData.length === 0) {
        const cacheList = await getStorage('listCache') || [];
        setListData(cacheList);
      }
    } finally {
      setRefreshing(false);
      setLoading(false);
    }
  };

  // 🔹 初始化加载列表
  useEffect(() => {
    loadListData();
  }, []);

  // 🔹 上拉加载更多(简易版)
  const loadMore = async () => {
    if (loading || listData.length >= 10) return; // 开发板最多加载10条
    setLoading(true);
    try {
      const res = await service.get('/users');
      const moreData = res.data.slice(listData.length, listData.length + 5);
      const newList = [...listData, ...moreData];
      setListData(newList);
      await setStorage('listCache', newList); // 更新缓存
    } catch (err) {
      console.error('❌ 加载更多失败:', err);
    } finally {
      setLoading(false);
    }
  };

  // 🔹 跳转到编辑表单
  const handleEdit = (item) => {
    setSelectedItem(item);
    setShowForm(true);
  };

  // 🔹 渲染列表项
  const renderListItem = ({ item }) => (
    <TouchableOpacity
      style={[styles.listItem, { width: ITEM_WIDTH }]}
      onPress={() => handleEdit(item)}
      activeOpacity={0.9}
    >
      <Text style={[styles.listName, isBoard && { fontSize: 20 }]}>
        {item.name}
      </Text>
      <Text style={[styles.listInfo, isBoard && { fontSize: 18 }]}>
        📧 {item.email}
      </Text>
      <Text style={[styles.listInfo, isBoard && { fontSize: 18 }]}>
        📞 {item.phone}
      </Text>
      <Text style={styles.editBtn}>
        编辑 ✏️
      </Text>
    </TouchableOpacity>
  );

  // 🔹 渲染UI
  return (
    <View style={styles.container}>
      {showForm ? (
        // 编辑表单
        <FormPage isEdit={true} editData={selectedItem} />
      ) : (
        <>
          <Text style={[styles.pageTitle, isBoard && { fontSize: 22 }]}>
            用户列表 📜(共{listData.length}条)
          </Text>
          
          {/* FlatList列表(核心) */}
          <FlatList
            data={listData}
            renderItem={renderListItem}
            keyExtractor={(item) => item.id.toString()}
            // 下拉刷新
            refreshing={refreshing}
            onRefresh={loadListData}
            // 上拉加载
            onEndReached={loadMore}
            onEndReachedThreshold={0.5} // 开发板增大触发阈值
            // 多终端布局:平板双列,手机/开发板单列
            numColumns={isTablet ? 2 : 1}
            // 刷新控件样式
            refreshControl={
              <RefreshControl
                refreshing={refreshing}
                tintColor="#007AFF"
                title="正在刷新..."
                titleColor="#666"
              />
            }
            // 空数据提示
            ListEmptyComponent={
              <Text style={styles.emptyText}>
                📭 暂无数据,请下拉刷新
              </Text>
            }
            // 加载更多提示
            ListFooterComponent={
              loading && <Text style={styles.loadingText}>加载中...🔄</Text>
            }
            contentContainerStyle={styles.listContainer}
          />
        </>
      )}
    </View>
  );
};

// 🎨 样式(多终端适配)
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8f8f8',
    paddingTop: 16
  },
  pageTitle: {
    fontSize: 20,
    fontWeight: '600',
    textAlign: 'center',
    marginBottom: 10,
    color: '#333'
  },
  listContainer: {
    paddingHorizontal: 10,
    gap: 10
  },
  listItem: {
    padding: isBoard ? 20 : 15,
    backgroundColor: '#fff',
    borderRadius: 8,
    marginBottom: 10,
    borderWidth: 1,
    borderColor: '#eee',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4
  },
  listName: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 5
  },
  listInfo: {
    fontSize: 14,
    color: '#666',
    marginTop: 3,
    lineHeight: isBoard ? 24 : 20
  },
  editBtn: {
    marginTop: 10,
    fontSize: 16,
    color: '#007AFF',
    fontWeight: '500'
  },
  emptyText: {
    fontSize: 16,
    color: '#999',
    textAlign: 'center',
    marginTop: 50
  },
  loadingText: {
    fontSize: 14,
    color: '#666',
    textAlign: 'center',
    padding: 10
  }
});

export default HomePage;
3.2 验证列表刷新功能

✅ 验证标准:

  1. 首页加载后,优先显示缓存数据💾,随后同步接口数据🌐;
  2. 下拉列表触发刷新🔽,刷新动画正常显示,列表数据重新加载;
  3. 点击列表项的“编辑 ✏️”按钮,跳转到编辑表单并回显对应数据;
  4. 更新数据后,下拉刷新列表,列表展示最新数据🔄;
  5. 平板端自动切换为双列布局📱,开发板列表项尺寸/字体增大。

3.4 步骤4:多终端适配与数据联动测试(10分钟)

针对不同鸿蒙终端优化,确保数据联动稳定。

4.1 开发板适配优化(关键)
// HomePage.js 补充开发板优化
const styles = StyleSheet.create({
  // 原有样式不变,新增/修改:
  listItem: {
    padding: isBoard ? 25 : 15, // 进一步增大开发板内边距
    backgroundColor: '#fff',
    borderRadius: 8,
    marginBottom: 15, // 增大间距
    borderWidth: 1,
    borderColor: '#eee',
    // 开发板增加阴影,提升触控识别
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.2,
    shadowRadius: 6
  },
  listName: {
    fontSize: isBoard ? 22 : 18, // 开发板字体更大
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 8
  },
  editBtn: {
    marginTop: 15,
    fontSize: isBoard ? 18 : 16, // 编辑按钮字体增大
    color: '#007AFF',
    fontWeight: '600' // 加粗,提升辨识度
  }
});

// 加载列表时,开发板减少数据量
const apiList = res.data.slice(0, isBoard ? 5 : 10); // 开发板只加载5条
4.2 多终端测试清单
终端类型 测试项 验证标准
💻 模拟器 全功能测试 列表刷新、数据更新、缓存联动、双列布局(平板模式)正常✅
📱 鸿蒙真机 性能+网络 列表滑动流畅🧩,网络波动时请求不超时,数据同步正常
🖥️ DAYU200开发板 触控+性能 列表项触控灵敏👆,无卡顿,数据更新/刷新无报错

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

# 1. 添加所有修改
git add .

# 2. 规范提交(符合Conventional Commits)
git commit -m "feat: 实现数据更新(PUT)与列表刷新,完成缓存与列表联动,适配多终端"

# 3. 推送至远程分支
git push origin feature-data-update-list-refresh

# 4. 验证提交
git log --oneline -5 # 查看提交记录📜

✅ 验证标准:

  1. 远程分支提交记录正常,提交信息清晰📝;
  2. 拉取代码后,多终端功能可正常运行🚀。

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

🚫 问题1:编辑表单数据回显失败,输入框无内容

解决方案:
  1. 确认isEdittrueeditData有值(打印日志排查:console.log('编辑数据:', editData));
  2. 检查数据字段名是否匹配(如editData.name而非editData.username);
  3. 鸿蒙终端需确保JSON解析正常:
    // 容错处理:
    const data = editData || await getStorage('formCache');
    if (data) {
      setName(data.name || ''); // 避免undefined
      setEmail(data.email || '');
      setPhone(data.phone || '');
    }
    

🚫 问题2:PUT请求提交失败,控制台提示「404 Not Found」

解决方案:
  1. 确认PUT请求地址正确:必须携带ID(如/users/1,而非/users);
  2. 检查测试接口是否支持PUT:https://jsonplaceholder.typicode.com/users/1可正常PUT;
  3. 验证请求体格式:确保是JSON格式,无多余字段。

🚫 问题3:列表下拉刷新无反应,刷新动画不显示

解决方案:
  1. 确认FlatListrefreshinguseState绑定(refreshing={refreshing});
  2. 检查onRefresh回调函数是否正确(onRefresh={loadListData});
  3. 开发板需简化刷新动画:
    <RefreshControl
      refreshing={refreshing}
      tintColor="#007AFF"
      // 移除复杂标题,减少性能消耗
    />
    

🚫 问题4:数据更新成功后,列表刷新仍显示旧数据

解决方案:
  1. 确认数据更新后,调用了setStorage('listCache', [])清空列表缓存;
  2. 检查loadListData函数是否重新读取接口数据(而非仅读取缓存);
  3. 避免闭包导致的数据未更新:
    // 错误示例:闭包捕获旧数据
    const loadListData = async () => {
      const res = await service.get('/users');
      setListData(res.data); // 正确:直接使用最新数据
    };
    

🚫 问题5:开发板列表卡顿、触控失效

解决方案:
  1. 减少列表渲染数据量(控制在5-8条);
  2. 增大列表项尺寸/间距/字体,提升触控识别率;
  3. 简化列表项样式:移除阴影/渐变等复杂样式;
  4. 优化渲染性能:使用React.memo包裹列表项组件。

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

本课时核心完成了数据更新、列表刷新与缓存联动,实现了应用的完整数据交互闭环,重点掌握4个核心要点:

  1. 🔑 数据更新的核心是「PUT请求+缓存同步」:复用表单组件实现新增/编辑模式切换,数据更新后同步更新缓存;
  2. 🔑 列表刷新的核心是「FlatList优化+数据联动」:通过refreshing/onRefresh实现下拉刷新,优先读取缓存提升速度;
  3. 🔑 数据一致性的核心是「单一数据源+缓存同步」:接口数据为最终标准,缓存仅作加速层,操作后同步更新缓存;
  4. 🔑 鸿蒙适配的核心是「差异化优化」:开发板优化性能/触控,真机优化网络/布局,确保多终端功能稳定。

本节课实现了“新增→缓存→列表展示→编辑更新→列表刷新”的完整闭环🔄,让应用具备了实用的用户数据管理能力。下一节课我们将学习应用异常处理与性能优化,解决崩溃、卡顿问题,为应用打包部署做准备🚀。


✅ 六、课后任务(必做)

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

独立完成「编辑表单开发+PUT请求+列表刷新+缓存联动」全流程,删除测试代码,确保功能正常。

📌 任务2:优化列表与表单功能🎯

  1. ✏️ 为列表项添加“编辑”按钮,点击跳转编辑表单并回显对应数据;
  2. 🚀 实现列表上拉加载更多功能(完整版,支持分页);
  3. 🗑️ 为编辑表单添加“删除”按钮,实现DELETE请求与缓存/列表同步。

📌 任务3:优化多终端适配✨

  1. 📱 完善平板端双列列表适配,根据屏幕旋转自动切换布局;
  2. 🖥️ 为开发板添加列表项触控反馈(如点击变色);
  3. 🌐 为所有网络请求添加失败重试机制(适配真机弱网环境)。

📌 任务4:全面测试🚀

确保模拟器、真机、开发板上数据更新、列表刷新、缓存联动无异常,无卡顿、无报错;预习RN应用异常处理、性能优化相关知识。


🌟 核心要点总结

  1. 数据更新:Axios PUT请求需携带数据ID,编辑模式复用表单组件,隐藏密码输入框保护隐私,更新后同步缓存;
  2. 列表刷新:FlatList通过refreshing/onRefresh实现下拉刷新,优先读取缓存提升速度,接口数据同步更新缓存;
  3. 数据一致性:接口数据为最终数据源,缓存仅作加速层,操作后同步更新缓存,避免数据错乱;
  4. 鸿蒙适配:开发板优化性能/触控(减少数据量、增大尺寸),真机优化网络/布局,确保多终端稳定。

若你在实操过程中遇到数据更新、列表刷新、缓存联动、鸿蒙适配相关的问题,欢迎在评论区留言💬!下一节课,我们一起解锁应用异常处理与性能优化,让应用更稳定、更流畅🚀!

关注我,后续课时持续更新,从0到1掌握RN兼容鸿蒙跨平台开发,夯实每一个核心开发环节🌟!

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

Logo

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

更多推荐