1. 项目背景与核心挑战
在电商应用开发中,订单确认页面是最为复杂的场景之一。它需要整合商品信息、收货地址、优惠券选择、配送时间和支付方式等多种数据,同时还要实时计算订单总价。当我们需要将这个功能同时部署到React Native和鸿蒙两个平台时,面临的挑战就更加显著。
我最近在开发一个跨平台电商应用时,就遇到了这样的需求。客户要求订单确认功能在iOS/Android和鸿蒙设备上提供完全一致的用户体验。经过多次迭代,我总结出了一套行之有效的方案,核心思路是将静态数据(商品、地址、优惠券列表)与动态选择状态(选中优惠券、支付方式、配送时间)彻底分离。
2. 数据模型设计与状态分离
2.1 静态数据模型定义
静态数据的特点是初始化后不会改变,主要包括商品信息、地址列表和可用优惠券。这些数据通常来自API接口,在页面生命周期内保持不变。
// 商品项类型 type CartItem = { id: string; // 商品项ID productId: string; // 商品ID name: string; // 商品名称 price: number; // 单价 quantity: number; // 数量 color: string; // 选中颜色 size: string; // 选中规格 imageUrl?: string; // 图片URL(可选) }; // 地址类型 type Address = { id: string; // 地址ID name: string; // 收件人 phone: string; // 电话 address: string; // 详细地址 isDefault: boolean; // 是否默认地址 }; // 优惠券类型 type Coupon = { id: string; // 优惠券ID code: string; // 优惠码 name: string; // 名称 discount: number; // 抵扣金额 minAmount: number; // 最低消费 expiryDate: string; // 过期时间 used: boolean; // 是否已使用 };这种设计的关键点在于:
- 每个模型都包含了业务所需的完整字段
- 使用TypeScript类型确保数据安全
- 可选字段(imageUrl)增强了灵活性
- 业务规则内置(如优惠券的minAmount)
2.2 动态状态管理
动态状态会随着用户操作而变化,主要包括:
// React Native中的状态声明 const [selectedCoupon, setSelectedCoupon] = useState<string | null>(null); const [paymentMethod, setPaymentMethod] = useState<string>('alipay'); const [deliveryTime, setDeliveryTime] = useState<string>('尽快送达'); // 鸿蒙中的对应实现 @State selectedCoupon: string | null = null; @State paymentMethod: string = 'alipay'; @State deliveryTime: string = '尽快送达';这种分离带来的优势:
- 性能优化:静态数据不会触发重渲染
- 逻辑清晰:业务规则与UI状态解耦
- 易于测试:可以单独测试状态逻辑
- 跨平台一致性:状态结构完全一致
3. 跨平台实现方案
3.1 React Native实现要点
在RN中,我们采用标准的Hooks方案管理状态:
const OrderConfirmScreen = () => { // 静态数据使用const声明,避免不必要的重渲染 const [cartItems] = useState<CartItem[]>([...]); const [address] = useState<Address>({...}); const [coupons] = useState<Coupon[]>([...]); // 动态状态 const [selectedCoupon, setSelectedCoupon] = useState<string | null>(null); // 其他动态状态... // 价格计算函数 const calculateTotal = () => { const subtotal = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); const discount = selectedCoupon ? coupons.find(c => c.id === selectedCoupon)?.discount || 0 : 0; return subtotal - discount; }; // 渲染逻辑... }关键优化点:
- 使用FlatList优化长列表性能
- 静态数据使用const声明
- 价格计算使用纯函数
- 条件渲染优化(如优惠券抵扣行的显示)
3.2 鸿蒙ArkUI适配方案
鸿蒙端需要将React逻辑转换为ArkUI语法,但核心数据模型和业务逻辑可以完全复用:
@Entry @Component struct OrderConfirmPage { // 静态数据 @State cartItems: CartItem[] = [...]; @State address: Address = {...}; @State coupons: Coupon[] = [...]; // 动态状态 @State selectedCoupon: string | null = null; // 其他状态... // 价格计算(与RN完全一致) calculateTotal(): number { const subtotal = this.cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); const discount = this.selectedCoupon ? this.coupons.find(c => c.id === this.selectedCoupon)?.discount || 0 : 0; return subtotal - discount; } // 构建函数 build() { Column() { // 页面结构... } } }适配注意事项:
- 使用LazyForEach替代FlatList
- 样式采用链式调用而非StyleSheet
- 交互事件使用onClick而非onPress
- 条件渲染使用if语句而非&&运算符
4. 核心业务逻辑实现
4.1 价格计算机制
价格计算是订单确认页的核心,我们的实现需要保证:
- 实时性:任何选择变化立即反映在总价上
- 准确性:严格遵循业务规则(如优惠券使用门槛)
- 性能:避免不必要的重复计算
// 通用计算逻辑(RN和鸿蒙完全一致) function calculateTotal( cartItems: CartItem[], coupons: Coupon[], selectedCoupon: string | null ): number { // 商品总价 const subtotal = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); // 优惠券抵扣 let discount = 0; if (selectedCoupon) { const coupon = coupons.find(c => c.id === selectedCoupon); if (coupon && subtotal >= coupon.minAmount) { discount = coupon.discount; } } // 运费逻辑预留 const shippingFee = 0; return subtotal - discount + shippingFee; }4.2 选项交互实现
对于配送时间、支付方式等选项,我们采用统一的交互模式:
// React Native实现 {['尽快送达', '工作日送货', '周末送货'].map(option => ( <TouchableOpacity key={option} style={[ styles.option, deliveryTime === option && styles.selectedOption ]} onPress={() => setDeliveryTime(option)} > <Text>{option}</Text> </TouchableOpacity> ))} // 鸿蒙实现 ['尽快送达', '工作日送货', '周末送货'].forEach(option => { Button() .backgroundColor(this.deliveryTime === option ? '#3b82f6' : '#f1f5f9') .onClick(() => this.deliveryTime = option) .child(Text(option)) })5. 性能优化实践
5.1 列表渲染优化
对于可能很长的商品列表和优惠券列表,我们采用了不同的优化策略:
React Native:
<FlatList data={cartItems} renderItem={renderCartItem} keyExtractor={item => item.id} initialNumToRender={5} windowSize={10} />鸿蒙:
LazyForEach( new MyDataSource(this.cartItems), (item: CartItem) => this.renderCartItem(item), (item: CartItem) => item.id )5.2 状态更新优化
通过分离静态数据和动态状态,我们最小化了不必要的重渲染:
// 好的做法 - 静态数据不会触发重渲染 const [cartItems] = useState<CartItem[]>([]); // 不好的做法 - 任何变化都会导致重渲染 const [state, setState] = useState({ cartItems: [], selectedCoupon: null });6. 样式与布局适配
6.1 跨平台样式方案
虽然RN使用StyleSheet而鸿蒙使用链式调用,但我们可以保持样式结构一致:
// React Native const styles = StyleSheet.create({ card: { backgroundColor: '#fff', borderRadius: 12, padding: 16, marginVertical: 8, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 4, elevation: 2 } }); // 鸿蒙 function cardStyles() { return { backgroundColor: '#fff', borderRadius: 12, padding: 16, margin: { top: 8, bottom: 8 }, shadow: { color: '#000', opacity: 0.1, radius: 4 } }; }6.2 响应式布局处理
对于需要适配不同屏幕尺寸的元素(如优惠券卡片),我们采用类似的策略:
// React Native const { width } = Dimensions.get('window'); const couponWidth = (width - 48) / 2 - 8; // 鸿蒙 aboutToAppear() { const windowSize = getWindowProperties().windowRect; this.windowWidth = windowSize.width; } // 使用时 .width((this.windowWidth - 48) / 2 - 8)7. 踩坑与解决方案
在实际开发中,我们遇到了几个典型问题:
问题1:鸿蒙LazyForEach的数据源处理
最初直接使用数组导致性能问题,后来实现了IDataSource接口:
class MyDataSource implements IDataSource { private list: CartItem[]; private listener: DataChangeListener; constructor(list: CartItem[]) { this.list = list; } totalCount(): number { return this.list.length; } getData(index: number): CartItem { return this.list[index]; } // 其他必要方法... }问题2:价格计算时机
最初在render中直接计算导致不必要的计算,改为:
// 好的做法 - 只在需要时计算 const total = useMemo(() => calculateTotal(cartItems, coupons, selectedCoupon), [cartItems, coupons, selectedCoupon]);问题3:鸿蒙条件渲染语法
鸿蒙不支持JSX的&&语法,需要改用if语句:
// 在build方法中 if (this.selectedCoupon) { // 渲染优惠券抵扣行 }8. 测试策略
为确保跨平台一致性,我们建立了完善的测试方案:
- 数据模型测试:验证两个平台的数据结构是否一致
- 计算逻辑测试:确保价格计算在所有边界条件下结果一致
- 交互测试:验证用户操作后的状态更新是否正确
- 性能测试:检查长列表滚动流畅度
// 示例测试用例 - 价格计算 describe('calculateTotal', () => { it('should apply coupon when meet minAmount', () => { const items = [{ price: 1000, quantity: 1 }]; const coupons = [{ id: '1', discount: 100, minAmount: 500 }]; expect(calculateTotal(items, coupons, '1')).toBe(900); }); it('should not apply coupon when not meet minAmount', () => { const items = [{ price: 400, quantity: 1 }]; const coupons = [{ id: '1', discount: 100, minAmount: 500 }]; expect(calculateTotal(items, coupons, '1')).toBe(400); }); });9. 项目总结与扩展思考
通过这个项目,我们验证了React Native和鸿蒙在复杂业务场景下实现跨平台一致性的可行性。核心业务逻辑的复用率达到了90%以上,主要差异集中在UI层和平台特定API的调用上。
对于未来类似项目,我会考虑:
- 引入状态管理库(如Redux/Zustand)进一步简化状态共享
- 开发通用的适配层抽象平台差异
- 建立更完善的自动化测试体系
- 探索更多代码共享的可能性,如验证逻辑、工具函数等
这种架构的另一个优势是便于后续维护。当业务规则变化时(如新增优惠券类型),我们只需要修改一处核心逻辑,两个平台都能受益。