【RN鸿蒙教学|第9课时】数据更新+列表刷新实战:缓存与列表联动+多终端兼容闭环
本文是React Native(RN)兼容开源鸿蒙(OpenHarmony)开发系列的第9课时,重点讲解数据更新与列表刷新的实战应用。主要内容包括: 核心目标:实现编辑表单开发、Axios PUT请求、列表组件优化与刷新功能,完成"缓存→数据更新→列表同步刷新"的完整联动。 技术要点: 复用表单组件实现编辑模式和数据回显 使用Axios PUT请求更新数据并同步缓存 优化Fla
🚀【RN鸿蒙教学|第9课时】数据更新+列表刷新实战:缓存与列表联动+多终端兼容闭环🎯
适配版本:RN 0.72.7 + OpenHarmony SDK 8.0 + @react-navigation/bottom-tabs@6.5.7 + axios@1.6.8
学习时长:90分钟⏱️
难度等级:⭐⭐⭐(进阶)
📋 目录导航 🧭
哈喽大家好~👋 欢迎来到React Native(RN)兼容开源鸿蒙(OpenHarmony)跨平台开发系列教学第9课时!
上一课时我们完成了表单优化与AsyncStorage数据持久化💾,实现了表单的完整输入、验证、缓存功能,让应用具备了本地数据存储能力。本节课我们将聚焦数据更新与列表刷新两大核心,搞定编辑表单开发、Axios PUT请求实现数据更新、RN列表组件优化与刷新功能,关键实现“本地缓存→数据更新→列表同步刷新”的完整联动🔄,同时解决鸿蒙多终端列表适配与数据同步异常问题,让应用形成完整的数据交互闭环,进一步提升应用实用性与流畅度🚀。
本课时核心目标是帮大家熟练掌握RN数据更新(PUT请求)与列表刷新的核心技巧,实现本地缓存与首页列表的联动同步,掌握鸿蒙多终端列表适配进阶方法,巩固组件复用、网络请求、状态管理和Git代码提交规范,解决数据联动与列表刷新的常见问题,夯实跨平台开发核心能力💪。
🎯 适合人群 & 课时目标
适合人群👨💻👩💻
已完成前8课时实操,掌握表单优化、AsyncStorage数据持久化、Axios POST请求、基础列表展示,想要学习数据更新、列表刷新与数据联动的开发者
课时目标(90分钟达成🎯)
- ✨ 完成编辑表单开发,复用原有表单组件,实现编辑时数据回显、验证与提交,适配鸿蒙多终端;
- 📤 熟练掌握Axios PUT请求用法,实现表单数据更新,联动AsyncStorage缓存同步更新;
- 📜 优化RN列表组件,实现列表下拉刷新、上拉加载(简易版),同步缓存与接口数据;
- 🔗 实现缓存、数据更新、列表刷新的完整联动,确保数据一致性,适配鸿蒙多终端;
- ✅ 解决数据更新、列表刷新、多终端适配的常见问题,完成功能开发、测试与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.js、storage.js、request.js可正常使用; - ✅ 打开DevEco Studio、VScode(加载RN工程)、Git Bash,确认所有工具可正常使用,多终端调试环境正常,RN版本保持0.72.7;
- ✅ 准备测试接口:
https://jsonplaceholder.typicode.com/users(支持GET/POST/PUT/DELETE,免费测试用🆓)。
⚠️ 关键提醒:
- 重点确认AsyncStorage缓存(formCache)、表单提交功能、首页列表展示正常;若列表无法展示、缓存读取失败,先回顾前序课时排查;
- 测试接口无需鉴权,可直接调用,优先验证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 |
渲染单个列表项 | 开发板增大列表项尺寸/字体📝,提升触控 |
列表与缓存联动逻辑
2.3 鸿蒙多终端适配进阶(列表+数据同步)
不同终端针对性优化,避免列表卡顿、数据同步异常:
| 终端类型 | 适配重点 | 具体操作 |
|---|---|---|
| 💻 模拟器 | 逻辑验证 | 测试数据更新、列表刷新、缓存联动的逻辑正确性 |
| 📱 鸿蒙真机(手机/平板) | 布局+网络 | 平板端双列展示列表📱,手机端单列;延长请求超时时间 |
| 🖥️ DAYU200开发板 | 性能+触控 | 简化列表样式/动效🎨;增大列表项尺寸;减少渲染数据量(5-8条) |
2.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
💡 测试技巧:手动修改
isEdit为true,测试数据回显:// 临时测试:在FormPage组件内添加 useEffect(() => { // 模拟传入编辑数据 setEditData({ id: 1, name: '测试用户', email: 'test@demo.com', phone: '13800138000' }); }, []);
✅ 验证标准:
- 编辑模式下,表单标题变为“编辑用户表单 ✏️”,提交按钮变为“更新数据 ✏️”;
- 编辑模式隐藏密码输入框(保护隐私🔒);
- 数据自动回显到姓名/邮箱/电话输入框,输入框可正常编辑;
- 开发板/真机上表单布局正常,触控无异常👆。
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请求与缓存同步
✅ 验证标准:
- 编辑模式下点击“更新数据 ✏️”,控制台输出
📤 PUT请求:/users/1和✅ PUT请求成功; - 更新成功后,控制台输出
✅ [存储成功] Key: formCache(缓存同步更新💾); - 关闭应用重新打开,表单回显更新后的数据;
- 新增模式仍正常使用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 验证列表刷新功能
✅ 验证标准:
- 首页加载后,优先显示缓存数据💾,随后同步接口数据🌐;
- 下拉列表触发刷新🔽,刷新动画正常显示,列表数据重新加载;
- 点击列表项的“编辑 ✏️”按钮,跳转到编辑表单并回显对应数据;
- 更新数据后,下拉刷新列表,列表展示最新数据🔄;
- 平板端自动切换为双列布局📱,开发板列表项尺寸/字体增大。
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 # 查看提交记录📜
✅ 验证标准:
- 远程分支提交记录正常,提交信息清晰📝;
- 拉取代码后,多终端功能可正常运行🚀。
🛠️ 四、常见问题与解决方案(10分钟,新手必看)
🚫 问题1:编辑表单数据回显失败,输入框无内容
解决方案:
- 确认
isEdit为true,editData有值(打印日志排查:console.log('编辑数据:', editData)); - 检查数据字段名是否匹配(如
editData.name而非editData.username); - 鸿蒙终端需确保JSON解析正常:
// 容错处理: const data = editData || await getStorage('formCache'); if (data) { setName(data.name || ''); // 避免undefined setEmail(data.email || ''); setPhone(data.phone || ''); }
🚫 问题2:PUT请求提交失败,控制台提示「404 Not Found」
解决方案:
- 确认PUT请求地址正确:必须携带ID(如
/users/1,而非/users); - 检查测试接口是否支持PUT:
https://jsonplaceholder.typicode.com/users/1可正常PUT; - 验证请求体格式:确保是JSON格式,无多余字段。
🚫 问题3:列表下拉刷新无反应,刷新动画不显示
解决方案:
- 确认
FlatList的refreshing与useState绑定(refreshing={refreshing}); - 检查
onRefresh回调函数是否正确(onRefresh={loadListData}); - 开发板需简化刷新动画:
<RefreshControl refreshing={refreshing} tintColor="#007AFF" // 移除复杂标题,减少性能消耗 />
🚫 问题4:数据更新成功后,列表刷新仍显示旧数据
解决方案:
- 确认数据更新后,调用了
setStorage('listCache', [])清空列表缓存; - 检查
loadListData函数是否重新读取接口数据(而非仅读取缓存); - 避免闭包导致的数据未更新:
// 错误示例:闭包捕获旧数据 const loadListData = async () => { const res = await service.get('/users'); setListData(res.data); // 正确:直接使用最新数据 };
🚫 问题5:开发板列表卡顿、触控失效
解决方案:
- 减少列表渲染数据量(控制在5-8条);
- 增大列表项尺寸/间距/字体,提升触控识别率;
- 简化列表项样式:移除阴影/渐变等复杂样式;
- 优化渲染性能:使用
React.memo包裹列表项组件。
📝 五、课堂小结(5分钟)
本课时核心完成了数据更新、列表刷新与缓存联动,实现了应用的完整数据交互闭环,重点掌握4个核心要点:
- 🔑 数据更新的核心是「PUT请求+缓存同步」:复用表单组件实现新增/编辑模式切换,数据更新后同步更新缓存;
- 🔑 列表刷新的核心是「FlatList优化+数据联动」:通过
refreshing/onRefresh实现下拉刷新,优先读取缓存提升速度; - 🔑 数据一致性的核心是「单一数据源+缓存同步」:接口数据为最终标准,缓存仅作加速层,操作后同步更新缓存;
- 🔑 鸿蒙适配的核心是「差异化优化」:开发板优化性能/触控,真机优化网络/布局,确保多终端功能稳定。
本节课实现了“新增→缓存→列表展示→编辑更新→列表刷新”的完整闭环🔄,让应用具备了实用的用户数据管理能力。下一节课我们将学习应用异常处理与性能优化,解决崩溃、卡顿问题,为应用打包部署做准备🚀。
✅ 六、课后任务(必做)
📌 任务1:复盘核心功能🔄
独立完成「编辑表单开发+PUT请求+列表刷新+缓存联动」全流程,删除测试代码,确保功能正常。
📌 任务2:优化列表与表单功能🎯
- ✏️ 为列表项添加“编辑”按钮,点击跳转编辑表单并回显对应数据;
- 🚀 实现列表上拉加载更多功能(完整版,支持分页);
- 🗑️ 为编辑表单添加“删除”按钮,实现DELETE请求与缓存/列表同步。
📌 任务3:优化多终端适配✨
- 📱 完善平板端双列列表适配,根据屏幕旋转自动切换布局;
- 🖥️ 为开发板添加列表项触控反馈(如点击变色);
- 🌐 为所有网络请求添加失败重试机制(适配真机弱网环境)。
📌 任务4:全面测试🚀
确保模拟器、真机、开发板上数据更新、列表刷新、缓存联动无异常,无卡顿、无报错;预习RN应用异常处理、性能优化相关知识。
🌟 核心要点总结
- ✨ 数据更新:Axios PUT请求需携带数据ID,编辑模式复用表单组件,隐藏密码输入框保护隐私,更新后同步缓存;
- ✨ 列表刷新:FlatList通过
refreshing/onRefresh实现下拉刷新,优先读取缓存提升速度,接口数据同步更新缓存; - ✨ 数据一致性:接口数据为最终数据源,缓存仅作加速层,操作后同步更新缓存,避免数据错乱;
- ✨ 鸿蒙适配:开发板优化性能/触控(减少数据量、增大尺寸),真机优化网络/布局,确保多终端稳定。
若你在实操过程中遇到数据更新、列表刷新、缓存联动、鸿蒙适配相关的问题,欢迎在评论区留言💬!下一节课,我们一起解锁应用异常处理与性能优化,让应用更稳定、更流畅🚀!
关注我,后续课时持续更新,从0到1掌握RN兼容鸿蒙跨平台开发,夯实每一个核心开发环节🌟!
欢迎加入开源鸿蒙跨平台社区,https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)