在移动应用开发中,地址管理是许多电商、外卖、物流等应用的重要功能模块。本文将深入分析一个功能完备的 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 - 是否显示地址表单

这种数据组织方式使得数据管理更加清晰,易于扩展和维护。


布局结构

应用界面采用了清晰的层次结构:

  • 头部 - 显示页面标题和操作按钮
  • 地址列表 - 显示已保存的地址
  • 地址表单 - 用于添加或编辑地址

这种布局结构符合用户的使用习惯,用户可以快速了解页面内容并进行操作。


在设计跨端地址管理应用时,需要特别关注以下几个方面:

  1. 组件 API 兼容性 - 确保使用的 React Native 组件在鸿蒙系统上有对应实现
  2. 样式系统差异 - 不同平台对样式的支持程度不同,需要确保样式在两端都能正常显示
  3. 图标系统 - 确保图标在不同平台上都能正常显示
  4. 表单控件 - 不同平台的表单控件可能存在差异
  5. 地理位置服务 - 不同平台的地理位置服务 API 可能存在差异

适配策略

针对上述问题,该实现采用了以下适配策略:

  1. 使用 React Native 核心组件 - 优先使用 React Native 内置的组件,如 View、Text、TouchableOpacity、ScrollView、TextInput 等
  2. 统一的样式定义 - 使用 StyleSheet.create 定义样式,确保样式在不同平台上的一致性
  3. Base64 图标 - 使用 Base64 编码的图标,确保图标在不同平台上的一致性
  4. 简化的表单控件 - 使用基本的 TextInput 组件,避免使用平台特定的表单控件
  5. 平台无关的地理位置 - 暂时使用手动输入的方式获取地址,避免依赖平台特定的地理位置 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: '确定' }
});

  1. 数据模型是跨端复用核心:强类型的地址数据模型保证了跨端数据层的一致性,是适配的基础;
  2. 业务逻辑可高度复用:地址管理的核心业务规则(默认地址保护、表单验证等)无需重构,仅需调整状态更新方式;
  3. 视图层需组件映射:React Native 组件需替换为鸿蒙 ArkUI 组件,保持交互逻辑一致;
  4. 样式系统需重新实现:鸿蒙通过内联样式 + @Styles 实现与 React Native StyleSheet 相同的视觉效果;
  5. 状态更新机制差异:鸿蒙直接赋值 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管理复杂状态。该方案适用于电商、外卖和物流等多种场景,具有良好的可扩展性和跨平台兼容性。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

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

更多推荐