在移动应用开发中,地址管理是许多电商、外卖、物流等应用的重要功能模块。本文将深入分析一个功能完备的 React Native 地址管理应用实现,探讨其架构设计、状态管理、数据结构以及跨端兼容性策略。

组件化

该实现采用了清晰的组件化架构,主要包含以下部分:

  • 主应用组件 (AddressEditApp) - 负责整体布局和状态管理
  • 地址列表渲染 - 负责渲染地址卡片列表
  • 地址表单 - 负责地址的添加和编辑
  • 功能按钮 - 提供添加、编辑、删除、设置默认地址等功能

这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态和业务逻辑,而各个功能部分负责具体的 UI 渲染,实现了关注点分离。

状态管理

AddressEditApp 组件使用 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 可能存在差

当前实现使用 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 向鸿蒙跨端迁移的典型业务场景案例。

该地址管理应用遵循移动端表单类应用的最佳实践,核心价值在于结构化数据管理表单交互逻辑状态驱动的视图切换,完整覆盖了收货地址管理的全业务场景。

1. 类型系统

应用采用 TypeScript 强类型设计,构建了标准化的地址数据模型,为跨端适配奠定了坚实的数据基础:

// 地址类型 - 覆盖收货地址全维度属性
type Address = {
  id: string;                // 唯一标识
  recipient: string;         // 收货人姓名
  phone: string;             // 手机号码
  province: string;          // 省份
  city: string;              // 城市
  district: string;          // 区县
  detailAddress: string;     // 详细地址
  isDefault: boolean;        // 是否默认地址
  tag: '家' | '公司' | '学校' | '其他'; // 地址标签
};
  • 数据模型设计亮点
    • 业务完整性:包含收货地址所需的全部核心字段,满足电商类应用的地址管理需求;
    • 强类型约束:tag 使用联合类型限制取值范围,避免非法标签值;
    • 状态标识:isDefault 布尔值明确标识默认地址,简化默认地址管理逻辑;
    • 标准化标识:id 字段保证每条地址数据的唯一性,是 CRUD 操作的基础;
  • 类型设计价值
    • 跨端数据复用:强类型定义使地址数据模型可 100% 复用于鸿蒙端,仅需适配视图层;
    • 业务逻辑清晰:类型定义明确区分了地址的不同属性,降低跨端适配的认知成本;
    • 错误提前暴露:编译期即可发现数据类型错误,减少运行时异常。

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);

地址保存(新增/编辑)
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);
};
(2)状态管理
  • 视图状态分离showForm 控制列表/表单视图切换,editingId 区分新增/编辑模式,状态职责清晰;
  • 不可变更新:地址列表更新采用数组 map/filter + 展开运算符,保证状态不可变性;
  • 业务规则内置:删除默认地址时自动设置新默认地址,体现电商地址管理的核心业务规则;
  • 表单重置机制resetForm 方法统一重置表单状态,避免状态残留;
  • 表单验证前置:保存地址前进行非空校验,提升用户体验;
  • 交互反馈完善:操作结果通过 Alert 提示,删除操作增加确认弹窗。

应用采用模块化的视图渲染方式,通过条件渲染实现列表/表单视图切换,同时注重交互细节和视觉反馈:

(1)多视图切换
{showForm ? renderAddressForm() : renderAddressList()}
  • 视图封装:将列表和表单视图封装为独立函数,代码结构清晰,便于维护和跨端适配;
  • 条件渲染:基于 showForm 状态精准控制视图切换,无冗余渲染。
(2)地址列表
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>
          )}
        </View>
        
        {/* 收货人信息 */}
        <View style={styles.addressInfo}>
          <Text style={styles.recipient}>{address.recipient}</Text>
          <Text style={styles.phone}>{address.phone}</Text>
        </View>
        
        {/* 完整地址 */}
        <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>
  </ScrollView>
);
(3)表单视图

表单视图采用结构化的输入控件布局,包含文本输入、地区选择、标签选择、复选框等典型表单元素:

<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.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 style={styles.tagSelector}>
  <TouchableOpacity 
    style={[
      styles.tagOption,
      currentAddress.tag === '家' && styles.selectedTag
    ]}
    onPress={() => selectTag('家')}
  >
    <Text style={[
      styles.tagOptionText,
      currentAddress.tag === '家' && styles.selectedTagText
    ]}>家</Text>
  </TouchableOpacity>
  {/* 其他标签选项... */}
</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>
</View>

鸿蒙端采用 ArkTS 语言 + ArkUI 组件库(Stage模型),与 React Native 的核心 API 映射关系如下:

React Native 核心API 鸿蒙 ArkTS 对应实现 适配关键说明
useState @State/@Link 状态声明语法替换,逻辑一致
SafeAreaView SafeArea 组件 + safeArea(true) 安全区域适配
View Column/Row/Stack 基础布局组件替换
Text Text 组件 属性基本兼容,样式语法调整
TouchableOpacity Button + stateEffect(false) 可点击组件替换,去除默认效果
ScrollView Scroll 组件 滚动容器替换
FlatList/数组 map List + ForEach 列表渲染适配
TextInput TextInput 组件 输入控件替换,事件绑定调整
StyleSheet.create @Styles/@Extend + 内联样式 样式体系重构
Alert.alert AlertDialog 弹窗交互替换
条件渲染(三元运算符/&&) if 语句 条件渲染语法适配
数组 map/filter filter + ForEach 数组操作适配
展开运算符(…) 对象直接赋值 状态更新语法适配

2. 鸿蒙端

// index.ets - 鸿蒙端入口文件
@Entry
@Component
struct AddressEditApp {
  // 核心状态 - 对应 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: '公司'
    }
  ];

  @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
  }
}

(1)核心 CRUD 方法适配
// 保存地址(新增/编辑)
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;
          // 强制更新UI
          this.addresses = [...this.addresses];
        }
      }
    }
  });
}

// 设置默认地址
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;
}
(2)地址列表
// 地址列表界面
@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;
        });
    }
    .padding(16)
    .width('100%')
    .flex(1);
  }
  .flex(1)
  .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';
  }
}
(3)表单视图
// 地址编辑界面
@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);
  }
  .flex(1)
  .width('100%');
}

// 输入项封装
@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));
}
(4)头部与底部
// 头部组件
@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
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%');
}

// 导航项封装
@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);
}

该收货地址管理应用的跨端适配实践表明,React Native 开发的表单类 CRUD 应用向鸿蒙平台迁移时,可实现极高的代码复用率(核心业务逻辑 90%+ 复用),视图层和样式系统是主要适配工作,但通过合理的结构化重构可完美解决。这种适配模式特别适合电商、政务、企业管理等包含大量表单 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 AddressEditApp = () => {
  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: '公司'
    }
  ]);

  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>
    </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',
  },
  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 AddressEditApp;


请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了一个基于React Native的地址管理应用实现,重点分析了其架构设计和功能实现。该应用采用组件化设计,包含地址列表、表单和功能按钮等模块,使用useState进行状态管理,并实现了地址增删改查、默认地址设置等核心功能。文章详细探讨了表单验证、数据类型定义和跨端兼容性考虑,并提出了使用FlatList优化性能、采用useReducer管理复杂状态等改进建议。该实现为电商、外卖等应用的地址管理模块提供了参考方案,具有清晰的代码结构和良好的可扩展性。

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

Logo

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

更多推荐