rn_for_openharmony商城项目app实战-地址列表实现
本文介绍了电商App中收货地址管理的实现方案。地址列表支持两种模式:管理模式(查看/编辑/删除)和选择模式(下单时选择地址)。采用React Native开发,通过FlatList渲染地址卡片,包含姓名、电话、详细地址等信息展示。核心功能包括:设置默认地址、删除地址(带二次确认)、地址选择和编辑。全局状态管理地址数据,区分不同场景处理交互逻辑。底部固定添加按钮方便用户新增地址,空状态提供友好引导。
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_buy
写在前面
收货地址是电商 App 的基础功能。用户下单时需要选择收货地址,所以要有个地方管理地址:查看、添加、编辑、删除、设置默认地址。
地址列表页有两种使用场景:一种是从个人中心进入,纯粹管理地址;另一种是从结算页进入,选择收货地址。这两种场景的交互略有不同,需要在代码里区分处理。
地址数据结构
先看看地址数据长什么样:
interface Address {
id: number;
name: string; // 收货人姓名
phone: string; // 手机号
province: string; // 省
city: string; // 市
district: string; // 区
detail: string; // 详细地址
isDefault: boolean; // 是否默认地址
}
地址拆成省、市、区、详细地址四个字段,方便后续做地址选择器。isDefault 标记是否为默认地址,下单时会自动选中默认地址。
引入依赖
import React from 'react';
import {View, Text, FlatList, StyleSheet, TouchableOpacity, Alert} from 'react-native';
import {useApp} from '../store/AppContext';
import {Header} from '../components/Header';
import {Empty} from '../components/Empty';
import {Address} from '../types';
用到了 Alert 做删除确认,FlatList 渲染地址列表。
组件主体
export const AddressListScreen = () => {
const {addresses, deleteAddress, setDefaultAddress, navigate, goBack, screenParams} = useApp();
const isSelecting = screenParams?.selecting;
从全局状态拿地址列表和操作方法。screenParams?.selecting 判断当前是"选择模式"还是"管理模式"。
如果是从结算页跳过来选地址,selecting 会是 true;如果是从个人中心进来管理地址,selecting 是 undefined 或 false。
两种模式有什么区别?
管理模式:点击地址卡片进入编辑页面。
选择模式:点击地址卡片选中这个地址,然后返回上一页。
选择地址处理
const handleSelect = (address: Address) => {
if (isSelecting && screenParams?.onSelect) {
screenParams.onSelect(address);
goBack();
}
};
选择模式下,点击地址会调用 screenParams.onSelect 回调,把选中的地址传回去,然后返回上一页。
这个回调是结算页跳转时传过来的,结算页会用这个地址更新收货地址。
为什么用回调而不是全局状态?
也可以用全局状态,比如设置一个
selectedAddress。但用回调更灵活,不同页面可以有不同的处理逻辑。而且不会污染全局状态。
删除地址
const handleDelete = (id: number) => {
Alert.alert('删除地址', '确定要删除这个地址吗?', [
{text: '取消', style: 'cancel'},
{text: '删除', style: 'destructive', onPress: () => deleteAddress(id)},
]);
};
删除是危险操作,需要二次确认。style: 'destructive' 让删除按钮显示成红色。
设置默认地址
const handleSetDefault = (id: number) => {
setDefaultAddress(id);
};
设置默认地址不需要确认,点击直接生效。Context 里的实现会把其他地址的 isDefault 设为 false,保证只有一个默认地址。
地址卡片渲染
const renderAddress = ({item}: {item: Address}) => (
<TouchableOpacity
style={styles.addressCard}
onPress={() => isSelecting ? handleSelect(item) : navigate('addressEdit', {address: item})}
>
整个卡片可点击。选择模式下点击选中地址,管理模式下点击进入编辑页面。
地址信息
<View style={styles.addressContent}>
<View style={styles.nameRow}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.phone}>{item.phone}</Text>
{item.isDefault && (
<View style={styles.defaultTag}>
<Text style={styles.defaultText}>默认</Text>
</View>
)}
</View>
<Text style={styles.detail} numberOfLines={2}>
{item.province} {item.city} {item.district} {item.detail}
</Text>
</View>
第一行显示姓名、电话,如果是默认地址还有个红色的"默认"标签。第二行显示完整地址,限制两行,超出显示省略号。
为什么姓名和电话放一行?
这是常见的设计,用户一眼就能看到这个地址是谁的、怎么联系。如果分两行会显得信息很散。
操作按钮
<View style={styles.actions}>
{!item.isDefault && (
<TouchableOpacity style={styles.actionBtn} onPress={() => handleSetDefault(item.id)}>
<Text style={styles.actionText}>设为默认</Text>
</TouchableOpacity>
)}
<TouchableOpacity style={styles.actionBtn} onPress={() => navigate('addressEdit', {address: item})}>
<Text style={styles.actionText}>编辑</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionBtn} onPress={() => handleDelete(item.id)}>
<Text style={[styles.actionText, styles.deleteText]}>删除</Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
);
底部是操作按钮:设为默认、编辑、删除。
"设为默认"按钮只在非默认地址上显示,已经是默认的就不需要这个按钮了。
删除按钮用红色,提醒用户这是危险操作。
页面主体
return (
<View style={styles.container}>
<Header title={isSelecting ? '选择收货地址' : '收货地址'} />
标题根据模式不同而不同。选择模式显示"选择收货地址",管理模式显示"收货地址"。
{addresses.length === 0 ? (
<Empty
icon="📍"
title="暂无收货地址"
subtitle="添加一个收货地址吧"
buttonText="添加地址"
onPress={() => navigate('addressEdit')}
/>
) : (
<FlatList
data={addresses}
keyExtractor={item => item.id.toString()}
contentContainerStyle={styles.list}
renderItem={renderAddress}
/>
)}
没有地址时显示空状态,引导用户添加地址。有地址时用 FlatList 渲染列表。
contentContainerStyle 里的 paddingBottom: 100 给底部的添加按钮留空间,不然最后一个地址会被按钮挡住。
添加地址按钮
<TouchableOpacity style={styles.addBtn} onPress={() => navigate('addressEdit')}>
<Text style={styles.addBtnIcon}>➕</Text>
<Text style={styles.addBtnText}>添加新地址</Text>
</TouchableOpacity>
</View>
);
};
底部固定一个添加按钮,蓝色背景很醒目。点击跳转到地址编辑页,不传 address 参数表示新增。
Context 里的地址操作
看看全局状态里地址相关的方法:
const [addresses, setAddresses] = useState<Address[]>(mockAddresses);
初始化了一些模拟数据,方便测试。
删除地址:
const deleteAddress = (id: number) => {
setAddresses(prev => prev.filter(a => a.id !== id));
};
用 filter 过滤掉指定 ID 的地址。
设置默认地址:
const setDefaultAddress = (id: number) => {
setAddresses(prev => prev.map(a => ({...a, isDefault: a.id === id})));
};
遍历所有地址,把指定 ID 的地址设为默认,其他的设为非默认。这样保证只有一个默认地址。
为什么不用两步操作?
也可以先把所有地址的
isDefault设为false,再把指定地址设为true。但那样要调用两次setAddresses,触发两次渲染。用map一次搞定更高效。
获取默认地址:
const defaultAddress = addresses.find(a => a.isDefault) || addresses[0] || null;
找到默认地址。如果没有设置默认,就用第一个地址。如果一个地址都没有,返回 null。
样式细节
地址卡片的样式:
addressCard: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
},
addressContent: {
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
paddingBottom: 12,
},
卡片用白色背景和圆角,地址信息和操作按钮之间有分割线。
默认标签的样式:
defaultTag: {
backgroundColor: '#e74c3c',
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 4,
marginLeft: 12,
},
defaultText: {fontSize: 11, color: '#fff', fontWeight: '600'},
红色背景白色文字,小巧醒目。
添加按钮的样式:
addBtn: {
position: 'absolute',
bottom: 32,
left: 16,
right: 16,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#3498db',
paddingVertical: 16,
borderRadius: 25,
},
绝对定位固定在底部,左右留 16 的边距,圆角做成胶囊形状。
滑动删除
当前删除要点击按钮,可以加个滑动删除的手势,更符合移动端习惯:
import {Swipeable} from 'react-native-gesture-handler';
const renderAddress = ({item}: {item: Address}) => (
<Swipeable
renderRightActions={() => (
<TouchableOpacity style={styles.swipeDelete} onPress={() => handleDelete(item.id)}>
<Text style={styles.swipeDeleteText}>删除</Text>
</TouchableOpacity>
)}
>
{/* 地址卡片内容 */}
</Swipeable>
);
左滑显示删除按钮,点击删除。需要安装 react-native-gesture-handler 库。
地址数量限制
用户可能添加很多地址,可以设置一个上限:
const MAX_ADDRESSES = 20;
// 添加按钮
{addresses.length < MAX_ADDRESSES ? (
<TouchableOpacity style={styles.addBtn} onPress={() => navigate('addressEdit')}>
<Text style={styles.addBtnText}>添加新地址</Text>
</TouchableOpacity>
) : (
<View style={styles.limitTip}>
<Text style={styles.limitText}>最多保存 {MAX_ADDRESSES} 个地址</Text>
</View>
)}
达到上限后隐藏添加按钮,显示提示文字。
写在最后
地址列表页的核心是两种模式的处理:管理模式和选择模式。通过页面参数区分,点击卡片时执行不同的逻辑。
地址的增删改查都在全局状态里实现,页面只负责展示和触发操作。设置默认地址要保证只有一个默认,用 map 一次遍历搞定。
下一篇写地址编辑页,实现地址的新增和修改。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)