基于React Native鸿蒙跨平台地址管理是许多电商、外卖、物流等应用的重要功能模块,实现了地址的添加、编辑、删除和设置默认等功能
本文探讨了一个React Native地址管理应用的实现方案,重点分析了其组件化架构、状态管理和跨平台适配策略。应用采用清晰的组件划分,包括主应用组件、地址列表、表单和功能按钮,实现了地址的增删改查和默认设置等核心功能。通过useState管理状态,TypeScript确保类型安全,并采用ScrollView渲染列表、基本表单验证等简化设计以适应跨平台需求。文章还提出了性能优化建议,如使用Flat
在移动应用开发中,地址管理是许多电商、外卖、物流等应用的重要功能模块。本文将深入分析一个功能完备的 React Native 地址管理应用实现,探讨其架构设计、状态管理、数据结构以及跨端zz策略。
组件化
该实现采用了清晰的组件化架构,主要包含以下部分:
- 主应用组件 (
AddressListApp) - 负责整体布局和状态管理 - 地址列表渲染 - 负责渲染地址卡片列表
- 地址表单 - 负责地址的添加和编辑
- 功能按钮 - 提供添加、编辑、删除、设置默认地址等功能
这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态和业务逻辑,而各个功能部分负责具体的 UI 渲染,实现了关注点分离。
状态管理
AddressListApp 组件使用 useState 钩子管理多个关键状态:
const [addresses, setAddresses] = useState<Address[]>([...]);
const [currentAddress, setCurrentAddress] = useState<Address>({...});
const [editingId, setEditingId] = useState<string | null>(null);
const [showForm, setShowForm] = useState(false);
这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了地址的添加、编辑、删除和设置默认等功能。使用 TypeScript 类型定义确保了数据结构的类型安全,提高了代码的可靠性。
地址管理功能
应用实现了完整的地址管理功能:
- 添加地址 - 填写地址表单并保存
- 编辑地址 - 修改现有地址信息
- 删除地址 - 删除不需要的地址
- 设置默认地址 - 将常用地址设为默认
这些功能覆盖了地址管理的基本需求,为用户提供了便捷的地址管理体验。
表单验证
应用实现了基本的表单验证:
const saveAddress = () => {
if (!currentAddress.recipient || !currentAddress.phone || !currentAddress.province ||
!currentAddress.city || !currentAddress.district || !currentAddress.detailAddress) {
Alert.alert('提示', '请填写完整地址信息');
return;
}
// 保存地址逻辑
};
这种实现方式确保了用户必须填写完整的地址信息才能保存,提高了数据的完整性和可靠性。
默认地址
应用实现了智能的默认地址管理:
- 新添加的地址可以设置为默认
- 可以将现有地址设为默认
- 删除默认地址时,自动将第一个地址设为默认
这种实现方式确保了始终有一个默认地址,提高了用户体验。
类型定义
该实现使用 TypeScript 定义了核心数据类型:
type Address = {
id: string;
recipient: string;
phone: string;
province: string;
city: string;
district: string;
detailAddress: string;
isDefault: boolean;
tag: '家' | '公司' | '学校' | '其他';
};
这个类型定义包含了地址的完整信息,包括:
- 基本信息 - 收件人、电话
- 地理位置 - 省、市、区、详细地址
- 状态标识 - 是否默认
- 标签 - 家、公司、学校、其他
使用 TypeScript 联合类型确保了 tag 的取值只能是预定义的几个选项之一,提高了代码的类型安全性。
数据
应用数据按照功能模块进行组织:
- addresses - 地址列表
- currentAddress - 当前编辑的地址
- editingId - 当前编辑的地址 ID
- showForm - 是否显示地址表单
这种数据组织方式使得数据管理更加清晰,易于扩展和维护。
布局结构
应用界面采用了清晰的层次结构:
- 头部 - 显示页面标题和操作按钮
- 地址列表 - 显示已保存的地址
- 地址表单 - 用于添加或编辑地址
这种布局结构符合用户的使用习惯,用户可以快速了解页面内容并进行操作。
在设计跨端地址管理应用时,需要特别关注以下几个方面:
- 组件 API 兼容性 - 确保使用的 React Native 组件在鸿蒙系统上有对应实现
- 样式系统差异 - 不同平台对样式的支持程度不同,需要确保样式在两端都能正常显示
- 图标系统 - 确保图标在不同平台上都能正常显示
- 表单控件 - 不同平台的表单控件可能存在差异
- 地理位置服务 - 不同平台的地理位置服务 API 可能存在差异
适配策略
针对上述问题,该实现采用了以下适配策略:
- 使用 React Native 核心组件 - 优先使用 React Native 内置的组件,如 View、Text、TouchableOpacity、ScrollView、TextInput 等
- 统一的样式定义 - 使用 StyleSheet.create 定义样式,确保样式在不同平台上的一致性
- Base64 图标 - 使用 Base64 编码的图标,确保图标在不同平台上的一致性
- 简化的表单控件 - 使用基本的 TextInput 组件,避免使用平台特定的表单控件
- 平台无关的地理位置 - 暂时使用手动输入的方式获取地址,避免依赖平台特定的地理位置 API
该实现采用了高度配置化的设计,通过数据结构可以灵活配置应用的行为:
- 地址标签 - 通过修改
tag类型和相关样式,可以轻松添加或修改地址标签 - 表单字段 - 通过修改
Address类型和表单渲染逻辑,可以轻松添加或修改表单字段 - 验证规则 - 通过修改表单验证逻辑,可以轻松添加或修改验证规则
这种设计使得应用能够轻松适应不同的使用场景,无需修改核心代码结构。
电商应用
该实现特别适合作为电商应用的地址管理模块,包含以下功能:
- 多地址管理 - 支持管理多个收货地址
- 默认地址设置 - 方便用户快速选择常用地址
- 地址标签 - 帮助用户区分不同用途的地址
- 地址验证 - 确保地址信息的完整性
外卖应用
对于外卖应用,该实现可以用于:
- 送餐地址管理 - 管理多个送餐地址
- 快速切换 - 方便用户在不同地址之间快速切换
- 地址标签 - 区分家、公司、学校等不同场景
- 地理位置服务 - 结合地理位置服务,自动填充地址信息
物流应用
对于物流应用,该实现可以用于:
- 发货地址管理 - 管理多个发货地址
- 收货地址管理 - 管理多个收货地址
- 地址验证 - 确保地址信息的准确性
- 批量操作 - 支持批量导入、导出地址
当前实现使用 ScrollView 渲染地址列表,可以考虑使用 FlatList 提高性能:
// 优化前
<ScrollView style={styles.content}>
{addresses.map(address => (
<AddressCard key={address.id} address={address} />
))}
</ScrollView>
// 优化后
<FlatList
data={addresses}
renderItem={({ item }) => (
<AddressCard address={item} />
)}
keyExtractor={item => item.id}
ListHeaderComponent={
<Text style={styles.title}>收货地址管理</Text>
}
style={styles.content}
/>
2. 状态管理
当前实现使用多个 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:
// 优化前
const [addresses, setAddresses] = useState<Address[]>([...]);
const [currentAddress, setCurrentAddress] = useState<Address>({...});
const [editingId, setEditingId] = useState<string | null>(null);
const [showForm, setShowForm] = useState(false);
// 优化后
type AppState = {
addresses: Address[];
currentAddress: Address;
editingId: string | null;
showForm: boolean;
};
type AppAction =
| { type: 'SET_ADDRESSES'; payload: Address[] }
| { type: 'SET_CURRENT_ADDRESS'; payload: Address }
| { type: 'SET_EDITING_ID'; payload: string | null }
| { type: 'SET_SHOW_FORM'; payload: boolean }
| { type: 'ADD_ADDRESS'; payload: Address }
| { type: 'UPDATE_ADDRESS'; payload: Address }
| { type: 'DELETE_ADDRESS'; payload: string }
| { type: 'SET_DEFAULT_ADDRESS'; payload: string };
const initialState: AppState = {
addresses: [...],
currentAddress: {...},
editingId: null,
showForm: false
};
const appReducer = (state: AppState, action: AppAction): AppState => {
switch (action.type) {
case 'SET_ADDRESSES':
return { ...state, addresses: action.payload };
case 'SET_CURRENT_ADDRESS':
return { ...state, currentAddress: action.payload };
case 'SET_EDITING_ID':
return { ...state, editingId: action.payload };
case 'SET_SHOW_FORM':
return { ...state, showForm: action.payload };
case 'ADD_ADDRESS':
return { ...state, addresses: [...state.addresses, action.payload] };
case 'UPDATE_ADDRESS':
return {
...state,
addresses: state.addresses.map(addr =>
addr.id === action.payload.id ? action.payload : addr
)
};
case 'DELETE_ADDRESS':
const updatedAddresses = state.addresses.filter(addr => addr.id !== action.payload);
// 如果删除的是默认地址,则将第一个地址设为默认
if (state.addresses.find(addr => addr.id === action.payload)?.isDefault && updatedAddresses.length > 0) {
updatedAddresses[0].isDefault = true;
}
return { ...state, addresses: updatedAddresses };
case 'SET_DEFAULT_ADDRESS':
return {
...state,
addresses: state.addresses.map(addr => ({
...addr,
isDefault: addr.id === action.payload
}))
};
default:
return state;
}
};
const [state, dispatch] = useReducer(appReducer, initialState);
3. 表单验证
可以增强表单验证,添加更多验证规则:
const validateForm = (address: Address): { isValid: boolean; errors: string[] } => {
const errors: string[] = [];
if (!address.recipient) {
errors.push('请填写收件人姓名');
}
if (!address.phone) {
errors.push('请填写联系电话');
} else if (!/^1[3-9]\d{9}$/.test(address.phone)) {
errors.push('请填写正确的手机号码');
}
if (!address.province) {
errors.push('请选择省份');
}
if (!address.city) {
errors.push('请选择城市');
}
if (!address.district) {
errors.push('请选择区县');
}
if (!address.detailAddress) {
errors.push('请填写详细地址');
}
return {
isValid: errors.length === 0,
errors
};
};
const saveAddress = () => {
const validation = validateForm(currentAddress);
if (!validation.isValid) {
Alert.alert('提示', validation.errors.join('\n'));
return;
}
// 保存地址逻辑
};
4. 地理位置
可以集成地理位置服务,自动填充地址信息:
import * as Location from 'expo-location';
const getCurrentLocation = async () => {
try {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
Alert.alert('提示', '需要位置权限才能自动填充地址');
return;
}
const location = await Location.getCurrentPositionAsync({});
const { latitude, longitude } = location.coords;
// 反向地理编码,获取地址信息
const [address] = await Location.reverseGeocodeAsync({
latitude,
longitude
});
if (address) {
setCurrentAddress(prev => ({
...prev,
province: address.region || '',
city: address.city || '',
district: address.district || '',
detailAddress: address.street || ''
}));
}
} catch (error) {
console.error('Error getting location:', error);
Alert.alert('提示', '获取位置信息失败,请手动填写地址');
}
};
本文深入分析了一个功能完备的 React Native 地址管理应用实现,从架构设计、状态管理、数据结构到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性。
理解这个增强版 React Native 收货地址管理应用的技术实现细节,同时掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心思路与实践方案。该应用在基础 CRUD 功能之上新增了使用提示模块,是电商类应用中典型的地址管理场景,具备完整的表单交互、状态管理和视觉设计体系。
该地址管理应用采用状态驱动视图的典型 React Native 架构,核心数据模型设计体现了电商地址管理的业务特性:
// 地址类型定义 - 覆盖电商收货地址全维度属性
type Address = {
id: string; // 唯一标识,用于CRUD操作
recipient: string; // 收货人姓名
phone: string; // 联系电话
province: string; // 省份
city: string; // 城市
district: string; // 区县
detailAddress: string; // 详细地址
isDefault: boolean; // 是否默认地址(核心业务标识)
tag: '家' | '公司' | '学校' | '其他'; // 地址标签(分类管理)
};
数据模型设计亮点:
- 业务完整性:包含电商场景下收货地址所需的全部核心字段,满足实际配送需求;
- 强类型约束:通过 TypeScript 联合类型限制
tag取值范围,避免非法标签值; - 状态标识清晰:
isDefault布尔值明确区分默认地址,简化默认地址管理逻辑; - 扩展性良好:每个字段都有明确的业务含义,便于后续扩展(如添加邮政编码、备注等)。
2. 状态管理
应用通过 React Hooks 实现精细化的状态管理,多状态协同完成地址的全生命周期管理:
// 核心状态定义
const [addresses, setAddresses] = useState<Address[]>([/* 初始化数据 */]); // 地址列表数据源
const [currentAddress, setCurrentAddress] = useState<Address>({/* 空对象 */}); // 当前编辑/新增的地址
const [editingId, setEditingId] = useState<string | null>(null); // 编辑状态标识(区分新增/编辑)
const [showForm, setShowForm] = useState(false); // 视图切换状态(列表/表单)
(1)地址保存逻辑(新增/编辑)
const saveAddress = () => {
// 表单完整性校验 - 前置业务规则
if (!currentAddress.recipient || !currentAddress.phone || !currentAddress.province ||
!currentAddress.city || !currentAddress.district || !currentAddress.detailAddress) {
Alert.alert('提示', '请填写完整地址信息');
return;
}
if (editingId) {
// 编辑模式:更新现有地址(不可变更新)
const updatedAddresses = addresses.map(addr =>
addr.id === editingId ? currentAddress : addr
);
setAddresses(updatedAddresses);
setEditingId(null);
} else {
// 新增模式:创建新地址
const newAddress = {
...currentAddress,
id: (addresses.length + 1).toString() // 简易ID生成策略
};
setAddresses([...addresses, newAddress]);
}
resetForm(); // 重置表单状态
Alert.alert('成功', '地址保存成功'); // 操作反馈
};
(2)地址删除逻辑
const deleteAddress = (id: string) => {
Alert.alert(
'确认删除',
'确定要删除这个地址吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '删除',
style: 'destructive',
onPress: () => {
const updatedAddresses = addresses.filter(addr => addr.id !== id);
setAddresses(updatedAddresses);
// 核心业务规则:删除默认地址时自动将第一个地址设为默认
if (addresses.find(addr => addr.id === id)?.isDefault && updatedAddresses.length > 0) {
updatedAddresses[0].isDefault = true;
}
}
}
]
);
};
(3)默认地址
const setDefaultAddress = (id: string) => {
// 不可变更新:重置所有地址的默认状态,仅目标地址设为默认
const updatedAddresses = addresses.map(addr => ({
...addr,
isDefault: addr.id === id
}));
setAddresses(updatedAddresses);
};
(1)视图切换
应用通过 showForm 状态实现列表/表单视图的无缝切换,保证用户操作的连贯性:
{showForm ? renderAddressForm() : renderAddressList()}
(2)增强型地址
const renderAddressList = () => (
<ScrollView style={styles.content}>
{/* 地址列表渲染... */}
{/* 新增使用提示卡片 - 提升用户体验 */}
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>使用提示</Text>
<Text style={styles.infoText}>• 设置默认地址可在结账时自动选择</Text>
<Text style={styles.infoText}>• 建议为家庭、公司等常用地点设置地址</Text>
<Text style={styles.infoText}>• 地址信息仅用于配送,请确保准确</Text>
</View>
</ScrollView>
);
(3)表单交互
- 地址标签选择器:通过条件样式实现选中态视觉反馈
<TouchableOpacity style={[ styles.tagOption, currentAddress.tag === '家' && styles.selectedTag ]} onPress={() => selectTag('家')} > <Text style={[ styles.tagOptionText, currentAddress.tag === '家' && styles.selectedTagText ]}>家</Text> </TouchableOpacity> - 自定义复选框:模拟原生复选框交互,实现设为默认地址功能
- 三级地区选择器:结构化布局,为后续联动选择预留扩展空间
- 多行文本输入:适配详细地址的长文本输入场景
1. 核心技术栈映射
| React Native 核心能力 | 鸿蒙 ArkTS 对应实现 | 适配关键说明 |
|---|---|---|
useState 状态管理 |
@State/@Link 装饰器 |
状态声明语法替换,逻辑一致 |
| 函数组件/JSX | @Component/@Builder + TSX |
组件化思想一致,语法调整 |
StyleSheet.create |
@Styles/@Extend + 内联样式 |
样式体系重构,视觉效果对齐 |
TouchableOpacity |
Button + stateEffect(false) |
可点击组件替换,去除默认样式 |
ScrollView |
Scroll 组件 |
滚动容器替换 |
TextInput |
TextInput 组件 |
输入控件事件绑定调整 |
数组 map 渲染列表 |
ForEach 循环渲染 |
列表渲染语法适配 |
Alert.alert |
AlertDialog 组件 |
弹窗交互替换 |
| 条件渲染(三元运算符) | if/else 条件渲染 |
视图控制语法适配 |
| 不可变状态更新 | 直接状态赋值 + 强制更新 | 状态更新机制调整 |
2. 鸿蒙端
// index.ets - 鸿蒙端入口文件
@Entry
@Component
struct AddressListApp {
// 核心状态定义(对应 React Native 的 useState)
@State addresses: Address[] = [
{
id: '1',
recipient: '张三',
phone: '13800138000',
province: '北京市',
city: '北京市',
district: '朝阳区',
detailAddress: '建国门外大街1号',
isDefault: true,
tag: '家'
},
{
id: '2',
recipient: '李四',
phone: '13900139000',
province: '上海市',
city: '上海市',
district: '浦东新区',
detailAddress: '陆家嘴环路1000号',
isDefault: false,
tag: '公司'
},
{
id: '3',
recipient: '王五',
phone: '13700137000',
province: '广州市',
city: '广州市',
district: '天河区',
detailAddress: '珠江新城花城大道',
isDefault: false,
tag: '其他'
}
];
@State currentAddress: Address = {
id: '',
recipient: '',
phone: '',
province: '',
city: '',
district: '',
detailAddress: '',
isDefault: false,
tag: '家'
};
@State editingId: string | null = null;
@State showForm: boolean = false;
// 类型定义(完全复用 React Native 的类型)
type Address = {
id: string;
recipient: string;
phone: string;
province: string;
city: string;
district: string;
detailAddress: string;
isDefault: boolean;
tag: '家' | '公司' | '学校' | '其他';
};
// 通用样式封装 - 替代 React Native 的 StyleSheet
@Styles
cardShadow() {
.shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
}
build() {
Column({ space: 0 }) {
// 头部组件
this.Header();
// 内容区域 - 视图切换
if (this.showForm) {
this.renderAddressForm();
} else {
this.renderAddressList();
}
// 底部导航
this.BottomNav();
}
.width('100%')
.height('100%')
.backgroundColor('#f8fafc')
.safeArea(true); // 对应 React Native 的 SafeAreaView
}
// 核心业务方法(复用 React Native 逻辑)
private saveAddress() {
// 表单验证(逻辑完全复用)
if (!this.currentAddress.recipient || !this.currentAddress.phone ||
!this.currentAddress.province || !this.currentAddress.city ||
!this.currentAddress.district || !this.currentAddress.detailAddress) {
AlertDialog.show({
title: '提示',
message: '请填写完整地址信息',
confirm: { value: '确定' }
});
return;
}
if (this.editingId) {
// 编辑模式
this.addresses = this.addresses.map(addr =>
addr.id === this.editingId ? this.currentAddress : addr
);
this.editingId = null;
} else {
// 新增模式
const newAddress: Address = {
...this.currentAddress,
id: (this.addresses.length + 1).toString()
};
this.addresses = [...this.addresses, newAddress];
}
this.resetForm();
// 操作成功提示
AlertDialog.show({
title: '成功',
message: '地址保存成功',
confirm: { value: '确定' }
});
}
private deleteAddress(id: string) {
AlertDialog.show({
title: '确认删除',
message: '确定要删除这个地址吗?',
cancel: { value: '取消' },
confirm: {
value: '删除',
action: () => {
const deletedAddress = this.addresses.find(addr => addr.id === id);
this.addresses = this.addresses.filter(addr => addr.id !== id);
// 默认地址保护逻辑(完全复用)
if (deletedAddress?.isDefault && this.addresses.length > 0) {
this.addresses[0].isDefault = true;
this.addresses = [...this.addresses]; // 强制更新UI
}
}
}
});
}
private setDefaultAddress(id: string) {
this.addresses = this.addresses.map(addr => ({
...addr,
isDefault: addr.id === id
}));
}
private editAddress(address: Address) {
this.currentAddress = { ...address };
this.editingId = address.id;
this.showForm = true;
}
private resetForm() {
this.currentAddress = {
id: '',
recipient: '',
phone: '',
province: '',
city: '',
district: '',
detailAddress: '',
isDefault: false,
tag: '家'
};
this.showForm = false;
}
private selectTag(tag: '家' | '公司' | '学校' | '其他') {
this.currentAddress.tag = tag;
}
// 头部组件
@Builder
Header() {
Row({ space: 0 }) {
// 返回按钮
Button('←')
.fontSize(20)
.fontColor('#64748b')
.backgroundColor(Color.Transparent)
.stateEffect(false)
.onClick(() => {
if (this.showForm) {
this.resetForm();
} else {
AlertDialog.show({
title: '提示',
message: '返回',
confirm: { value: '确定' }
});
}
});
// 标题
Text(this.showForm ? (this.editingId ? '编辑地址' : '新增地址') : '地址管理')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.marginLeft('auto')
.marginRight('auto');
// 占位元素
Blank()
.width(20);
}
.padding(20)
.backgroundColor('#ffffff')
.borderBottom({ width: 1, color: '#e2e8f0' })
.width('100%');
}
// 地址列表视图
@Builder
renderAddressList() {
Scroll() {
Column({ space: 16 }) {
Text('收货地址管理')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.textAlign(TextAlign.Center)
.marginBottom(20)
.width('100%');
// 地址列表渲染
ForEach(this.addresses, (address: Address) => {
Column({ space: 12 }) {
// 地址头部(标签 + 默认标识)
Row({ space: 0 }) {
// 地址标签
Text(address.tag)
.fontSize(12)
.fontWeight(FontWeight.Medium)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(12)
.backgroundColor(this.getTagBgColor(address.tag))
.fontColor(this.getTagTextColor(address.tag));
// 默认标签
if (address.isDefault) {
Text('默认')
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor('#ef4444')
.marginLeft('auto');
}
}
.width('100%');
// 收货人信息
Row({ space: 0 }) {
Text(address.recipient)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b');
Text(address.phone)
.fontSize(16)
.fontColor('#64748b')
.marginLeft('auto');
}
.width('100%')
.marginBottom(8);
// 完整地址
Text(`${address.province}${address.city}${address.district}${address.detailAddress}`)
.fontSize(14)
.fontColor('#64748b')
.lineHeight(20)
.width('100%')
.marginBottom(12);
// 操作按钮
Row({ space: 8 }) {
Button(address.isDefault ? '取消默认' : '设为默认')
.fontSize(14)
.fontColor('#3b82f6')
.backgroundColor('#f1f5f9')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(6)
.stateEffect(false)
.onClick(() => this.setDefaultAddress(address.id));
Button('编辑')
.fontSize(14)
.fontColor('#3b82f6')
.backgroundColor('#f1f5f9')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(6)
.stateEffect(false)
.onClick(() => this.editAddress(address));
Button('删除')
.fontSize(14)
.fontColor('#ef4444')
.backgroundColor('#fee2e2')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(6)
.stateEffect(false)
.onClick(() => this.deleteAddress(address.id));
}
.justifyContent(FlexAlign.End)
.width('100%');
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(16)
.cardShadow() // 复用样式
.width('100%')
.marginBottom(16);
});
// 添加新地址按钮
Button('+ 添加新地址')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#ffffff')
.backgroundColor('#3b82f6')
.paddingVertical(16)
.borderRadius(12)
.width('100%')
.stateEffect(false)
.marginTop(20)
.onClick(() => {
this.resetForm();
this.showForm = true;
});
// 使用提示卡片
Column({ space: 8 }) {
Text('使用提示')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.marginBottom(12);
Text('• 设置默认地址可在结账时自动选择')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 建议为家庭、公司等常用地点设置地址')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
Text('• 地址信息仅用于配送,请确保准确')
.fontSize(14)
.fontColor('#64748b')
.lineHeight(22);
}
.backgroundColor('#ffffff')
.borderRadius(12)
.padding(16)
.cardShadow()
.width('100%')
.marginTop(20);
}
.padding(16)
.width('100%');
}
.flex(1)
.width('100%');
}
// 地址表单视图
@Builder
renderAddressForm() {
Scroll() {
Column({ space: 20 }) {
Text(this.editingId ? '编辑地址' : '添加新地址')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
.textAlign(TextAlign.Center)
.width('100%');
// 收货人姓名
this.renderInputGroup(
'收货人姓名',
this.currentAddress.recipient,
(value: string) => this.currentAddress.recipient = value,
'请输入收货人姓名'
);
// 手机号码
this.renderInputGroup(
'手机号码',
this.currentAddress.phone,
(value: string) => this.currentAddress.phone = value,
'请输入手机号码',
InputType.Number
);
// 所在地区
Column({ space: 8 }) {
Text('所在地区')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b');
Row({ space: 8 }) {
Button(this.currentAddress.province || '省份')
.fontSize(16)
.fontColor('#64748b')
.backgroundColor('#f1f5f9')
.padding(12)
.borderRadius(8)
.flex(1)
.stateEffect(false);
Button(this.currentAddress.city || '城市')
.fontSize(16)
.fontColor('#64748b')
.backgroundColor('#f1f5f9')
.padding(12)
.borderRadius(8)
.flex(1)
.stateEffect(false);
Button(this.currentAddress.district || '区县')
.fontSize(16)
.fontColor('#64748b')
.backgroundColor('#f1f5f9')
.padding(12)
.borderRadius(8)
.flex(1)
.stateEffect(false);
}
.width('100%');
}
// 详细地址
Column({ space: 8 }) {
Text('详细地址')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b');
TextInput({
placeholder: '请输入详细地址,如街道、门牌号等',
text: this.currentAddress.detailAddress,
onChange: (value: InputAttribute) => {
this.currentAddress.detailAddress = value.text;
}
})
.backgroundColor('#ffffff')
.border({ width: 1, color: '#cbd5e1' })
.borderRadius(8)
.padding(12)
.fontSize(16)
.fontColor('#1e293b')
.height(100)
.textAlign(TextAlign.Top);
}
// 地址标签
Column({ space: 8 }) {
Text('地址标签')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b');
Row({ space: 8 }) {
this.renderTagOption('家');
this.renderTagOption('公司');
this.renderTagOption('学校');
this.renderTagOption('其他');
}
.width('100%');
}
// 设为默认地址
Row({ space: 8 }) {
Button() {
if (this.currentAddress.isDefault) {
Text('✓')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#3b82f6');
}
}
.width(20)
.height(20)
.border({ width: 2, color: '#cbd5e1' })
.borderRadius(10)
.backgroundColor(Color.Transparent)
.stateEffect(false)
.onClick(() => {
this.currentAddress.isDefault = !this.currentAddress.isDefault;
});
Text('设为默认地址')
.fontSize(16)
.fontColor('#1e293b');
}
// 操作按钮
Row({ space: 8 }) {
Button('保存地址')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#ffffff')
.backgroundColor('#3b82f6')
.paddingVertical(16)
.borderRadius(12)
.flex(1)
.stateEffect(false)
.onClick(() => this.saveAddress());
Button('取消')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#64748b')
.backgroundColor('#f1f5f9')
.paddingVertical(16)
.borderRadius(12)
.flex(1)
.stateEffect(false)
.onClick(() => this.resetForm());
}
.width('100%');
}
.padding(16)
.width('100%');
}
.flex(1)
.width('100%');
}
// 底部导航
@Builder
BottomNav() {
Row({ space: 0 }) {
this.renderNavItem('🏠', '首页');
this.renderNavItem('🛒', '购物车');
this.renderNavItem('📍', '地址');
this.renderNavItem('👤', '我的');
}
.backgroundColor('#ffffff')
.borderTop({ width: 1, color: '#e2e8f0' })
.paddingVertical(12)
.justifyContent(FlexAlign.SpaceAround)
.width('100%');
}
// 辅助方法
private getTagBgColor(tag: string): string {
switch (tag) {
case '家': return '#dbeafe';
case '公司': return '#dcfce7';
case '学校': return '#fef3c7';
case '其他': return '#ddd6fe';
default: return '#f1f5f9';
}
}
private getTagTextColor(tag: string): string {
switch (tag) {
case '家': return '#3b82f6';
case '公司': return '#22c55e';
case '学校': return '#f59e0b';
case '其他': return '#8b5cf6';
default: return '#64748b';
}
}
@Builder
renderInputGroup(label: string, value: string, onChange: (value: string) => void, placeholder: string, inputType: InputType = InputType.Normal) {
Column({ space: 8 }) {
Text(label)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b');
TextInput({
placeholder: placeholder,
text: value,
type: inputType,
onChange: (inputValue: InputAttribute) => {
onChange(inputValue.text);
}
})
.backgroundColor('#ffffff')
.border({ width: 1, color: '#cbd5e1' })
.borderRadius(8)
.padding(12)
.fontSize(16)
.fontColor('#1e293b')
.width('100%');
}
.width('100%');
}
@Builder
renderTagOption(tag: '家' | '公司' | '学校' | '其他') {
const isSelected = this.currentAddress.tag === tag;
Button(tag)
.fontSize(16)
.fontColor(isSelected ? '#3b82f6' : '#64748b')
.fontWeight(isSelected ? FontWeight.Medium : FontWeight.Normal)
.backgroundColor(isSelected ? '#dbeafe' : '#f1f5f9')
.border({ width: isSelected ? 1 : 0, color: '#3b82f6' })
.padding(12)
.borderRadius(8)
.flex(1)
.stateEffect(false)
.onClick(() => this.selectTag(tag));
}
@Builder
renderNavItem(icon: string, text: string) {
Button() {
Column({ space: 4 }) {
Text(icon)
.fontSize(20)
.fontColor('#94a3b8');
Text(text)
.fontSize(12)
.fontColor('#94a3b8');
}
}
.backgroundColor(Color.Transparent)
.flex(1)
.stateEffect(false);
}
}
(1)状态更新
React Native 采用不可变更新模式,而鸿蒙支持直接状态赋值:
// React Native
setAddresses([...addresses, newAddress]);
// 鸿蒙
this.addresses = [...this.addresses, newAddress];
// 鸿蒙数组元素更新(需强制刷新)
this.addresses[0].isDefault = true;
this.addresses = [...this.addresses]; // 强制UI更新
(2)条件样式
React Native 的样式数组转换为鸿蒙的动态样式计算:
// React Native
<Text style={[
styles.tag,
address.tag === '家' && styles.tagHome
]}>
// 鸿蒙
Text(address.tag)
.backgroundColor(this.getTagBgColor(address.tag))
.fontColor(this.getTagTextColor(address.tag));
(3)表单输入
// React Native
<TextInput
value={currentAddress.recipient}
onChangeText={(text) => setCurrentAddress({...currentAddress, recipient: text})}
/>
// 鸿蒙
TextInput({
text: this.currentAddress.recipient,
onChange: (value: InputAttribute) => {
this.currentAddress.recipient = value.text;
}
});
(4)弹窗交互
// React Native
Alert.alert('提示', '请填写完整地址信息');
// 鸿蒙
AlertDialog.show({
title: '提示',
message: '请填写完整地址信息',
confirm: { value: '确定' }
});
- 数据模型是跨端复用核心:强类型的地址数据模型保证了跨端数据层的一致性,是适配的基础;
- 业务逻辑可高度复用:地址管理的核心业务规则(默认地址保护、表单验证等)无需重构,仅需调整状态更新方式;
- 视图层需组件映射:React Native 组件需替换为鸿蒙 ArkUI 组件,保持交互逻辑一致;
- 样式系统需重新实现:鸿蒙通过内联样式 +
@Styles实现与 React Native StyleSheet 相同的视觉效果; - 状态更新机制差异:鸿蒙直接赋值 vs React 不可变更新是核心语法差异,需特别注意数组更新的强制刷新。
该收货地址管理应用的跨端适配实践验证了 React Native 向鸿蒙迁移的可行性与高效性,对于电商类表单 CRUD 应用,可实现较高的代码复用率,同时保证一致的用户体验和业务逻辑。这种适配模式特别适合需要快速覆盖多端的企业级应用开发。
真实演示案例代码:
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, TextInput } from 'react-native';
// Base64 图标库
const ICONS_BASE64 = {
home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
office: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
school: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
other: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
location: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
save: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
delete: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
edit: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};
const { width, height } = Dimensions.get('window');
// 地址类型
type Address = {
id: string;
recipient: string;
phone: string;
province: string;
city: string;
district: string;
detailAddress: string;
isDefault: boolean;
tag: '家' | '公司' | '学校' | '其他';
};
const AddressListApp = () => {
const [addresses, setAddresses] = useState<Address[]>([
{
id: '1',
recipient: '张三',
phone: '13800138000',
province: '北京市',
city: '北京市',
district: '朝阳区',
detailAddress: '建国门外大街1号',
isDefault: true,
tag: '家'
},
{
id: '2',
recipient: '李四',
phone: '13900139000',
province: '上海市',
city: '上海市',
district: '浦东新区',
detailAddress: '陆家嘴环路1000号',
isDefault: false,
tag: '公司'
},
{
id: '3',
recipient: '王五',
phone: '13700137000',
province: '广州市',
city: '广州市',
district: '天河区',
detailAddress: '珠江新城花城大道',
isDefault: false,
tag: '其他'
}
]);
const [currentAddress, setCurrentAddress] = useState<Address>({
id: '',
recipient: '',
phone: '',
province: '',
city: '',
district: '',
detailAddress: '',
isDefault: false,
tag: '家'
});
const [editingId, setEditingId] = useState<string | null>(null);
const [showForm, setShowForm] = useState(false);
// 添加或编辑地址
const saveAddress = () => {
if (!currentAddress.recipient || !currentAddress.phone || !currentAddress.province ||
!currentAddress.city || !currentAddress.district || !currentAddress.detailAddress) {
Alert.alert('提示', '请填写完整地址信息');
return;
}
if (editingId) {
// 更新现有地址
const updatedAddresses = addresses.map(addr =>
addr.id === editingId ? currentAddress : addr
);
setAddresses(updatedAddresses);
setEditingId(null);
} else {
// 添加新地址
const newAddress = {
...currentAddress,
id: (addresses.length + 1).toString()
};
setAddresses([...addresses, newAddress]);
}
resetForm();
Alert.alert('成功', '地址保存成功');
};
// 删除地址
const deleteAddress = (id: string) => {
Alert.alert(
'确认删除',
'确定要删除这个地址吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '删除',
style: 'destructive',
onPress: () => {
const updatedAddresses = addresses.filter(addr => addr.id !== id);
setAddresses(updatedAddresses);
// 如果删除的是默认地址,则将第一个地址设为默认
if (addresses.find(addr => addr.id === id)?.isDefault && updatedAddresses.length > 0) {
updatedAddresses[0].isDefault = true;
}
}
}
]
);
};
// 设置默认地址
const setDefaultAddress = (id: string) => {
const updatedAddresses = addresses.map(addr => ({
...addr,
isDefault: addr.id === id
}));
setAddresses(updatedAddresses);
};
// 编辑地址
const editAddress = (address: Address) => {
setCurrentAddress(address);
setEditingId(address.id);
setShowForm(true);
};
// 重置表单
const resetForm = () => {
setCurrentAddress({
id: '',
recipient: '',
phone: '',
province: '',
city: '',
district: '',
detailAddress: '',
isDefault: false,
tag: '家'
});
setShowForm(false);
};
// 选择标签
const selectTag = (tag: '家' | '公司' | '学校' | '其他') => {
setCurrentAddress({ ...currentAddress, tag });
};
// 地址列表界面
const renderAddressList = () => (
<ScrollView style={styles.content}>
<Text style={styles.title}>收货地址管理</Text>
{addresses.map(address => (
<View key={address.id} style={styles.addressCard}>
<View style={styles.addressHeader}>
<View style={styles.tagContainer}>
<Text style={[
styles.tag,
address.tag === '家' && styles.tagHome,
address.tag === '公司' && styles.tagOffice,
address.tag === '学校' && styles.tagSchool,
address.tag === '其他' && styles.tagOther
]}>
{address.tag}
</Text>
</View>
{address.isDefault && (
<Text style={styles.defaultTag}>默认</Text>
)}
</div>
<View style={styles.addressInfo}>
<Text style={styles.recipient}>{address.recipient}</Text>
<Text style={styles.phone}>{address.phone}</Text>
</div>
<Text style={styles.fullAddress}>
{address.province}{address.city}{address.district}{address.detailAddress}
</Text>
<View style={styles.addressActions}>
<TouchableOpacity
style={styles.actionButton}
onPress={() => setDefaultAddress(address.id)}
>
<Text style={styles.actionText}>
{address.isDefault ? '取消默认' : '设为默认'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => editAddress(address)}
>
<Text style={styles.actionText}>编辑</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.deleteButton}
onPress={() => deleteAddress(address.id)}
>
<Text style={styles.deleteText}>删除</Text>
</TouchableOpacity>
</View>
</View>
))}
<TouchableOpacity
style={styles.addButton}
onPress={() => {
resetForm();
setShowForm(true);
}}
>
<Text style={styles.addButtonText}>+ 添加新地址</Text>
</TouchableOpacity>
{/* 提示信息 */}
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>使用提示</Text>
<Text style={styles.infoText}>• 设置默认地址可在结账时自动选择</Text>
<Text style={styles.infoText}>• 建议为家庭、公司等常用地点设置地址</Text>
<Text style={styles.infoText}>• 地址信息仅用于配送,请确保准确</Text>
</View>
</ScrollView>
);
// 地址编辑界面
const renderAddressForm = () => (
<ScrollView style={styles.content}>
<Text style={styles.formTitle}>{editingId ? '编辑地址' : '添加新地址'}</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>收货人姓名</Text>
<TextInput
style={styles.input}
value={currentAddress.recipient}
onChangeText={(text) => setCurrentAddress({...currentAddress, recipient: text})}
placeholder="请输入收货人姓名"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>手机号码</Text>
<TextInput
style={styles.input}
value={currentAddress.phone}
onChangeText={(text) => setCurrentAddress({...currentAddress, phone: text})}
placeholder="请输入手机号码"
keyboardType="phone-pad"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>所在地区</Text>
<View style={styles.regionSelector}>
<TouchableOpacity style={styles.regionButton}>
<Text style={styles.regionButtonText}>{currentAddress.province || '省份'}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.regionButton}>
<Text style={styles.regionButtonText}>{currentAddress.city || '城市'}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.regionButton}>
<Text style={styles.regionButtonText}>{currentAddress.district || '区县'}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>详细地址</Text>
<TextInput
style={[styles.input, styles.textArea]}
value={currentAddress.detailAddress}
onChangeText={(text) => setCurrentAddress({...currentAddress, detailAddress: text})}
placeholder="请输入详细地址,如街道、门牌号等"
multiline
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>地址标签</Text>
<View style={styles.tagSelector}>
<TouchableOpacity
style={[
styles.tagOption,
currentAddress.tag === '家' && styles.selectedTag
]}
onPress={() => selectTag('家')}
>
<Text style={[
styles.tagOptionText,
currentAddress.tag === '家' && styles.selectedTagText
]}>家</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.tagOption,
currentAddress.tag === '公司' && styles.selectedTag
]}
onPress={() => selectTag('公司')}
>
<Text style={[
styles.tagOptionText,
currentAddress.tag === '公司' && styles.selectedTagText
]}>公司</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.tagOption,
currentAddress.tag === '学校' && styles.selectedTag
]}
onPress={() => selectTag('学校')}
>
<Text style={[
styles.tagOptionText,
currentAddress.tag === '学校' && styles.selectedTagText
]}>学校</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.tagOption,
currentAddress.tag === '其他' && styles.selectedTag
]}
onPress={() => selectTag('其他')}
>
<Text style={[
styles.tagOptionText,
currentAddress.tag === '其他' && styles.selectedTagText
]}>其他</Text>
</TouchableOpacity>
</div>
</View>
<View style={styles.checkboxContainer}>
<TouchableOpacity
style={styles.checkbox}
onPress={() => setCurrentAddress({...currentAddress, isDefault: !currentAddress.isDefault})}
>
{currentAddress.isDefault && <Text style={styles.checkboxCheck}>✓</Text>}
</TouchableOpacity>
<Text style={styles.checkboxLabel}>设为默认地址</Text>
</div>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.saveButton}
onPress={saveAddress}
>
<Text style={styles.saveButtonText}>保存地址</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.cancelButton}
onPress={resetForm}
>
<Text style={styles.cancelButtonText}>取消</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<TouchableOpacity onPress={() => showForm ? resetForm() : Alert.alert('返回')}>
<Text style={styles.backButton}>←</Text>
</TouchableOpacity>
<Text style={styles.headerTitle}>
{showForm ? (editingId ? '编辑地址' : '新增地址') : '地址管理'}
</Text>
<View style={styles.headerRight} />
</View>
{/* 内容区域 */}
{showForm ? renderAddressForm() : renderAddressList()}
{/* 底部导航 */}
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>🏠</Text>
<Text style={styles.navText}>首页</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>🛒</Text>
<Text style={styles.navText}>购物车</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>📍</Text>
<Text style={styles.navText}>地址</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>👤</Text>
<Text style={styles.navText}>我的</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
backButton: {
fontSize: 20,
color: '#64748b',
},
headerTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
},
headerRight: {
width: 20,
},
content: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 20,
textAlign: 'center',
},
formTitle: {
fontSize: 24,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 20,
textAlign: 'center',
},
addressCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
addressHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
tagContainer: {
flexDirection: 'row',
},
tag: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
fontSize: 12,
fontWeight: '500',
},
tagHome: {
backgroundColor: '#dbeafe',
color: '#3b82f6',
},
tagOffice: {
backgroundColor: '#dcfce7',
color: '#22c55e',
},
tagSchool: {
backgroundColor: '#fef3c7',
color: '#f59e0b',
},
tagOther: {
backgroundColor: '#ddd6fe',
color: '#8b5cf6',
},
defaultTag: {
fontSize: 12,
color: '#ef4444',
fontWeight: '500',
},
addressInfo: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 8,
},
recipient: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
},
phone: {
fontSize: 16,
color: '#64748b',
},
fullAddress: {
fontSize: 14,
color: '#64748b',
lineHeight: 20,
marginBottom: 12,
},
addressActions: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
actionButton: {
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
backgroundColor: '#f1f5f9',
marginRight: 8,
},
actionText: {
fontSize: 14,
color: '#3b82f6',
},
deleteButton: {
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
backgroundColor: '#fee2e2',
},
deleteText: {
fontSize: 14,
color: '#ef4444',
},
addButton: {
backgroundColor: '#3b82f6',
paddingVertical: 16,
borderRadius: 12,
alignItems: 'center',
marginTop: 20,
},
addButtonText: {
fontSize: 16,
fontWeight: 'bold',
color: '#ffffff',
},
inputGroup: {
marginBottom: 20,
},
label: {
fontSize: 14,
fontWeight: '500',
color: '#1e293b',
marginBottom: 8,
},
input: {
backgroundColor: '#ffffff',
borderWidth: 1,
borderColor: '#cbd5e1',
borderRadius: 8,
padding: 12,
fontSize: 16,
color: '#1e293b',
},
textArea: {
height: 100,
textAlignVertical: 'top',
},
regionSelector: {
flexDirection: 'row',
justifyContent: 'space-between',
},
regionButton: {
flex: 1,
backgroundColor: '#f1f5f9',
padding: 12,
borderRadius: 8,
alignItems: 'center',
marginRight: 8,
},
regionButtonText: {
fontSize: 16,
color: '#64748b',
},
tagSelector: {
flexDirection: 'row',
justifyContent: 'space-between',
},
tagOption: {
flex: 1,
backgroundColor: '#f1f5f9',
padding: 12,
borderRadius: 8,
alignItems: 'center',
marginRight: 8,
},
selectedTag: {
backgroundColor: '#dbeafe',
borderWidth: 1,
borderColor: '#3b82f6',
},
tagOptionText: {
fontSize: 16,
color: '#64748b',
},
selectedTagText: {
color: '#3b82f6',
fontWeight: '500',
},
checkboxContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 20,
},
checkbox: {
width: 20,
height: 20,
borderWidth: 2,
borderColor: '#cbd5e1',
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
marginRight: 8,
},
checkboxCheck: {
fontSize: 14,
color: '#3b82f6',
fontWeight: 'bold',
},
checkboxLabel: {
fontSize: 16,
color: '#1e293b',
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
saveButton: {
flex: 1,
backgroundColor: '#3b82f6',
paddingVertical: 16,
borderRadius: 12,
alignItems: 'center',
marginRight: 8,
},
saveButtonText: {
fontSize: 16,
fontWeight: 'bold',
color: '#ffffff',
},
cancelButton: {
flex: 1,
backgroundColor: '#f1f5f9',
paddingVertical: 16,
borderRadius: 12,
alignItems: 'center',
marginLeft: 8,
},
cancelButtonText: {
fontSize: 16,
fontWeight: 'bold',
color: '#64748b',
},
infoCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginTop: 20,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
infoTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 12,
},
infoText: {
fontSize: 14,
color: '#64748b',
lineHeight: 22,
marginBottom: 8,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center',
flex: 1,
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
navText: {
fontSize: 12,
color: '#94a3b8',
},
});
export default AddressListApp;

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

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

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

本文探讨了一个React Native地址管理应用的实现方案,重点分析了其组件化架构、状态管理和跨平台适配策略。应用采用清晰的组件划分,包括主应用组件、地址列表、表单和功能按钮,实现了地址的增删改查和默认设置等核心功能。通过useState管理状态,TypeScript确保类型安全,并采用ScrollView渲染列表、基本表单验证等简化设计以适应跨平台需求。文章还提出了性能优化建议,如使用FlatList替代ScrollView,以及采用useReducer管理复杂状态。该方案适用于电商、外卖和物流等多种场景,具有良好的可扩展性和跨平台兼容性。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)