React Native外卖应用:订餐和配送系统实现

React Native外卖应用:订餐和配送系统实现

【免费下载链接】react-native 一个用于构建原生移动应用程序的 JavaScript 库,可以用于构建 iOS 和 Android 应用程序,支持多种原生移动平台,如 iOS,Android,React Native 等。 【免费下载链接】react-native 项目地址: https://gitcode.com/GitHub_Trending/re/react-native

引言:外卖应用开发的痛点与解决方案

你是否曾为构建流畅的外卖应用而烦恼?用户等待时间长、订单状态同步延迟、地图定位不准等问题是否让你头疼?本文将带你从零开始,使用React Native构建一个高效、稳定的外卖订餐和配送系统,解决这些核心痛点。

读完本文,你将能够:

  • 掌握React Native核心组件在实际项目中的应用
  • 实现用户认证与订单管理功能
  • 集成地图服务与实时配送跟踪
  • 优化应用性能,提升用户体验
  • 解决外卖应用开发中的常见问题

1. 项目架构设计

1.1 系统整体架构

外卖应用通常包含用户端、商家端和配送端三个主要部分。本文将重点介绍用户端的实现,包括用户注册登录、商品浏览、下单支付、订单跟踪等功能。

mermaid

1.2 技术栈选择

本项目将使用以下技术栈:

技术说明
React Native跨平台移动应用开发框架
Redux状态管理库
React Navigation路由管理库
AxiosHTTP请求库
AsyncStorage本地数据存储
react-native-maps地图组件
react-native-svgSVG图标支持
Formik + Yup表单处理与验证

1.3 项目目录结构

外卖应用/
├── src/
│   ├── api/            # API请求
│   ├── assets/         # 静态资源
│   ├── components/     # 自定义组件
│   ├── constants/      # 常量定义
│   ├── hooks/          # 自定义钩子
│   ├── navigation/     # 路由配置
│   ├── screens/        # 屏幕组件
│   │   ├── Auth/       # 认证相关
│   │   ├── Home/       # 首页相关
│   │   ├── Shop/       # 商家相关
│   │   ├── Cart/       # 购物车
│   │   ├── Order/      # 订单相关
│   │   └── Profile/    # 个人中心
│   ├── store/          # Redux状态管理
│   ├── styles/         # 样式定义
│   ├── types/          # TypeScript类型定义
│   └── utils/          # 工具函数
├── App.tsx             # 应用入口
└── index.js            # 程序入口

2. 环境搭建与项目初始化

2.1 开发环境配置

首先,确保你的开发环境已经配置好React Native开发所需的所有依赖。如果还没有配置,可以按照以下步骤进行:

# 安装React Native CLI
npm install -g react-native-cli

# 创建新项目
react-native init FoodDeliveryApp

# 进入项目目录
cd FoodDeliveryApp

# 安装依赖
npm install redux react-redux redux-thunk axios react-navigation react-native-maps formik yup

2.2 项目初始化

修改App.tsx文件,设置应用的基本结构:

import React from 'react';
import { SafeAreaView, StatusBar, StyleSheet, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { Provider } from 'react-redux';
import store from './src/store';
import MainNavigator from './src/navigation/MainNavigator';

const App = () => {
  return (
    <Provider store={store}>
      <NavigationContainer>
        <SafeAreaView style={styles.container}>
          <StatusBar barStyle="dark-content" />
          <MainNavigator />
        </SafeAreaView>
      </NavigationContainer>
    </Provider>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

export default App;

3. 核心功能实现

3.1 用户认证模块

用户认证是外卖应用的基础功能,包括注册、登录、密码找回等。我们将使用Formik和Yup进行表单处理和验证。

// src/screens/Auth/LoginScreen.tsx
import React from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';
import { useDispatch } from 'react-redux';
import { login } from '../../store/actions/authActions';

const LoginSchema = Yup.object().shape({
  phone: Yup.string()
    .required('手机号不能为空')
    .matches(/^1[3-9]\d{9}$/, '请输入正确的手机号'),
  password: Yup.string()
    .required('密码不能为空')
    .min(6, '密码至少6位'),
});

const LoginScreen = ({ navigation }) => {
  const dispatch = useDispatch();

  const handleLogin = async (values) => {
    try {
      await dispatch(login(values.phone, values.password));
      navigation.navigate('Main');
    } catch (error) {
      Alert.alert('登录失败', error.message);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>欢迎回来</Text>
      
      <Formik
        initialValues={{ phone: '', password: '' }}
        validationSchema={LoginSchema}
        onSubmit={handleLogin}
      >
        {({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (
          <View>
            <View style={styles.inputContainer}>
              <TextInput
                style={styles.input}
                placeholder="请输入手机号"
                keyboardType="phone-pad"
                onChangeText={handleChange('phone')}
                onBlur={handleBlur('phone')}
                value={values.phone}
              />
              {touched.phone && errors.phone && (
                <Text style={styles.errorText}>{errors.phone}</Text>
              )}
            </View>
            
            <View style={styles.inputContainer}>
              <TextInput
                style={styles.input}
                placeholder="请输入密码"
                secureTextEntry
                onChangeText={handleChange('password')}
                onBlur={handleBlur('password')}
                value={values.password}
              />
              {touched.password && errors.password && (
                <Text style={styles.errorText}>{errors.password}</Text>
              )}
            </View>
            
            <TouchableOpacity 
              style={styles.loginButton} 
              onPress={handleSubmit}
            >
              <Text style={styles.loginButtonText}>登录</Text>
            </TouchableOpacity>
            
            <View style={styles.footer}>
              <TouchableOpacity onPress={() => navigation.navigate('ForgotPassword')}>
                <Text style={styles.forgotPassword}>忘记密码?</Text>
              </TouchableOpacity>
              
              <View style={styles.registerContainer}>
                <Text style={styles.registerText}>还没有账号?</Text>
                <TouchableOpacity onPress={() => navigation.navigate('Register')}>
                  <Text style={styles.registerButton}>立即注册</Text>
                </TouchableOpacity>
              </View>
            </View>
          </View>
        )}
      </Formik>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    marginBottom: 40,
    textAlign: 'center',
  },
  inputContainer: {
    marginBottom: 20,
  },
  input: {
    height: 50,
    borderColor: '#ddd',
    borderWidth: 1,
    borderRadius: 8,
    paddingHorizontal: 15,
    fontSize: 16,
  },
  errorText: {
    color: 'red',
    fontSize: 12,
    marginTop: 5,
    marginLeft: 5,
  },
  loginButton: {
    backgroundColor: '#FF4D4F',
    height: 50,
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 20,
  },
  loginButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  footer: {
    alignItems: 'center',
  },
  forgotPassword: {
    color: '#1890FF',
    marginBottom: 20,
  },
  registerContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  registerText: {
    color: '#666',
    fontSize: 14,
  },
  registerButton: {
    color: '#FF4D4F',
    fontSize: 14,
    marginLeft: 5,
    fontWeight: 'bold',
  },
});

export default LoginScreen;

3.2 商品列表与详情

外卖应用的核心是商品展示和选择。我们将使用FlatList组件来展示商品列表,并实现下拉刷新和上拉加载更多功能。

// src/screens/Home/ShopDetailScreen.tsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, Image, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
import { useDispatch } from 'react-redux';
import { addToCart } from '../../store/actions/cartActions';
import { useNavigation } from '@react-navigation/native';

const ShopDetailScreen = ({ route }) => {
  const { shop } = route.params;
  const [goods, setGoods] = useState([]);
  const [categories, setCategories] = useState([]);
  const [activeCategory, setActiveCategory] = useState('');
  const [loading, setLoading] = useState(true);
  const dispatch = useDispatch();
  const navigation = useNavigation();

  useEffect(() => {
    // 获取商品数据
    fetchGoodsData();
    // 设置导航栏标题
    navigation.setOptions({ title: shop.name });
  }, []);

  const fetchGoodsData = async () => {
    try {
      setLoading(true);
      // 模拟API请求
      setTimeout(() => {
        // 假设从API获取商品数据
        const mockCategories = [
          { id: 'all', name: '全部' },
          { id: 'recommended', name: '推荐' },
          { id: 'food', name: '美食' },
          { id: 'drink', name: '饮品' },
          { id: 'dessert', name: '甜点' },
        ];
        
        const mockGoods = [
          { 
            id: '1', 
            name: '招牌汉堡', 
            price: 28, 
            originalPrice: 35, 
            image: 'https://example.com/hamburger.jpg',
            category: 'food',
            sales: 120,
            description: '精选牛肉,新鲜蔬菜,特制酱料'
          },
          { 
            id: '2', 
            name: '可乐', 
            price: 5, 
            originalPrice: 6, 
            image: 'https://example.com/cola.jpg',
            category: 'drink',
            sales: 300,
            description: '冰爽可口'
          },
          // 更多商品...
        ];
        
        setCategories(mockCategories);
        setGoods(mockGoods);
        setActiveCategory(mockCategories[0].id);
        setLoading(false);
      }, 1000);
    } catch (error) {
      console.error('获取商品数据失败:', error);
      setLoading(false);
    }
  };

  const filteredGoods = activeCategory === 'all' 
    ? goods 
    : goods.filter(item => item.category === activeCategory);

  const renderCategoryItem = ({ item }) => (
    <TouchableOpacity
      style={[
        styles.categoryItem,
        activeCategory === item.id ? styles.activeCategory : null
      ]}
      onPress={() => setActiveCategory(item.id)}
    >
      <Text 
        style={[
          styles.categoryText,
          activeCategory === item.id ? styles.activeCategoryText : null
        ]}
      >
        {item.name}
      </Text>
    </TouchableOpacity>
  );

  const renderGoodsItem = ({ item }) => (
    <View style={styles.goodsItem}>
      <Image source={{ uri: item.image }} style={styles.goodsImage} />
      <View style={styles.goodsInfo}>
        <Text style={styles.goodsName}>{item.name}</Text>
        <Text style={styles.goodsDescription} numberOfLines={1}>{item.description}</Text>
        <View style={styles.goodsPriceContainer}>
          <Text style={styles.goodsPrice}>¥{item.price.toFixed(2)}</Text>
          {item.originalPrice > item.price && (
            <Text style={styles.goodsOriginalPrice}>¥{item.originalPrice.toFixed(2)}</Text>
          )}
          {item.sales > 0 && (
            <Text style={styles.goodsSales}>{item.sales}人已买</Text>
          )}
        </View>
      </View>
      <TouchableOpacity 
        style={styles.addButton}
        onPress={() => dispatch(addToCart(item, 1))}
      >
        <Text style={styles.addButtonText}>+</Text>
      </TouchableOpacity>
    </View>
  );

  return (
    <View style={styles.container}>
      {/* 店铺头部信息 */}
      <View style={styles.shopHeader}>
        <Image source={{ uri: shop.image }} style={styles.shopImage} />
        <View style={styles.shopInfo}>
          <Text style={styles.shopName}>{shop.name}</Text>
          <View style={styles.shopTags}>
            <Text style={styles.tag}>外卖</Text>
            <Text style={styles.tag}>到店</Text>
          </View>
          <View style={styles.shopStats}>
            <Text style={styles.statItem}>月售{shop.monthSales}</Text>
            <Text style={styles.statItem}>配送费¥{shop.deliveryFee}</Text>
            <Text style={styles.statItem}>配送{shop.deliveryTime}</Text>
          </View>
        </View>
      </View>

      {/* 分类导航 */}
      <FlatList
        data={categories}
        renderItem={renderCategoryItem}
        keyExtractor={item => item.id}
        horizontal
        showsHorizontalScrollIndicator={false}
        style={styles.categoryList}
      />

      {/* 商品列表 */}
      <FlatList
        data={filteredGoods}
        renderItem={renderGoodsItem}
        keyExtractor={item => item.id}
        contentContainerStyle={styles.goodsList}
        ListHeaderComponent={activeCategory !== 'all' ? (
          <Text style={styles.categoryTitle}>
            {categories.find(c => c.id === activeCategory)?.name}
          </Text>
        ) : null}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  shopHeader: {
    backgroundColor: 'white',
    padding: 15,
    flexDirection: 'row',
  },
  shopImage: {
    width: 80,
    height: 80,
    borderRadius: 8,
    marginRight: 10,
  },
  shopInfo: {
    flex: 1,
  },
  shopName: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  shopTags: {
    flexDirection: 'row',
    marginBottom: 5,
  },
  tag: {
    backgroundColor: '#f0f0f0',
    paddingHorizontal: 5,
    paddingVertical: 2,
    borderRadius: 3,
    fontSize: 12,
    marginRight: 5,
  },
  shopStats: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  statItem: {
    fontSize: 12,
    color: '#666',
    marginRight: 10,
  },
  categoryList: {
    backgroundColor: 'white',
    paddingVertical: 10,
  },
  categoryItem: {
    paddingHorizontal: 15,
    paddingVertical: 5,
    marginHorizontal: 5,
    borderRadius: 20,
  },
  activeCategory: {
    backgroundColor: '#FFF0F0',
  },
  categoryText: {
    fontSize: 14,
  },
  activeCategoryText: {
    color: '#FF4D4F',
    fontWeight: 'bold',
  },
  goodsList: {
    padding: 10,
  },
  goodsItem: {
    backgroundColor: 'white',
    borderRadius: 10,
    padding: 10,
    marginBottom: 10,
    flexDirection: 'row',
    alignItems: 'center',
  },
  goodsImage: {
    width: 80,
    height: 80,
    borderRadius: 8,
  },
  goodsInfo: {
    flex: 1,
    marginLeft: 10,
  },
  goodsName: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  goodsDescription: {
    fontSize: 12,
    color: '#666',
    marginBottom: 5,
  },
  goodsPriceContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  goodsPrice: {
    fontSize: 16,
    color: '#FF4D4F',
    fontWeight: 'bold',
  },
  goodsOriginalPrice: {
    fontSize: 12,
    color: '#999',
    textDecorationLine: 'line-through',
    marginLeft: 5,
  },
  goodsSales: {
    fontSize: 12,
    color: '#999',
    marginLeft: 10,
  },
  addButton: {
    width: 30,
    height: 30,
    borderRadius: 15,
    backgroundColor: '#FF4D4F',
    justifyContent: 'center',
    alignItems: 'center',
  },
  addButtonText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
  },
});

export default ShopDetailScreen;

3.3 购物车功能

购物车是外卖应用的核心功能之一,用于临时存储用户选择的商品,计算总价,并支持修改商品数量、删除商品等操作。

// src/components/Cart/CartItem.tsx
import React from 'react';
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
import { useDispatch } from 'react-redux';
import { addToCart, removeFromCart } from '../../store/actions/cartActions';

const CartItem = ({ item }) => {
  const dispatch = useDispatch();

  const handleIncrease = () => {
    dispatch(addToCart(item, 1));
  };

  const handleDecrease = () => {
    if (item.quantity > 1) {
      dispatch(addToCart(item, -1));
    } else {
      dispatch(removeFromCart(item.id));
    }
  };

  return (
    <View style={styles.container}>
      <Image source={{ uri: item.image }} style={styles.image} />
      <View style={styles.infoContainer}>
        <Text style={styles.name}>{item.name}</Text>
        <Text style={styles.price}>¥{item.price.toFixed(2)}</Text>
      </View>
      <View style={styles.quantityContainer}>
        <TouchableOpacity onPress={handleDecrease} style={styles.quantityButton}>
          <Text style={styles.quantityButtonText}>-</Text>
        </TouchableOpacity>
        <Text style={styles.quantity}>{item.quantity}</Text>
        <TouchableOpacity onPress={handleIncrease} style={styles.quantityButton}>
          <Text style={styles.quantityButtonText}>+</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  image: {
    width: 60,
    height: 60,
    borderRadius: 8,
  },
  infoContainer: {
    flex: 1,
    marginLeft: 10,
  },
  name: {
    fontSize: 16,
    marginBottom: 5,
  },
  price: {
    fontSize: 14,
    color: '#FF4D4F',
    fontWeight: 'bold',
  },
  quantityContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  quantityButton: {
    width: 30,
    height: 30,
    borderRadius: 15,
    backgroundColor: '#f5f5f5',
    justifyContent: 'center',
    alignItems: 'center',
  },
  quantityButtonText: {
    fontSize: 16,
    color: '#333',
  },
  quantity: {
    marginHorizontal: 15,
    fontSize: 16,
    fontWeight: 'bold',
  },
});

export default CartItem;

3.4 订单跟踪功能

订单跟踪功能允许用户实时查看订单状态和配送进度。我们将使用react-native-maps组件来显示配送员位置和配送路线。

// src/screens/Order/OrderTrackingScreen.tsx
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Image, TouchableOpacity, Dimensions } from 'react-native';
import MapView, { Marker, Polyline } from 'react-native-maps';
import { useNavigation } from '@react-navigation/native';

const { width, height } = Dimensions.get('window');

const OrderTrackingScreen = ({ route }) => {
  const { order } = route.params;
  const [orderStatus, setOrderStatus] = useState('delivering');
  const [courierLocation, setCourierLocation] = useState({
    latitude: 39.908823,
    longitude: 116.397470,
  });
  const [restaurantLocation, setRestaurantLocation] = useState({
    latitude: 39.908823,
    longitude: 116.397470,
  });
  const [userLocation, setUserLocation] = useState({
    latitude: 39.915823,
    longitude: 116.407470,
  });
  const [polylineCoordinates, setPolylineCoordinates] = useState([
    restaurantLocation,
    { latitude: 39.910823, longitude: 116.399470 },
    { latitude: 39.912823, longitude: 116.402470 },
    { latitude: 39.914823, longitude: 116.405470 },
    userLocation,
  ]);
  const [estimatedTime, setEstimatedTime] = useState(15); // 预计送达时间(分钟)
  const navigation = useNavigation();

  useEffect(() => {
    // 模拟配送员位置更新
    const interval = setInterval(() => {
      updateCourierLocation();
      // 更新预计送达时间
      if (estimatedTime > 0) {
        setEstimatedTime(prev => prev - 1);
      }
    }, 30000); // 每30秒更新一次

    return () => clearInterval(interval);
  }, [estimatedTime]);

  const updateCourierLocation = () => {
    // 模拟配送员移动
    const stepIndex = Math.max(0, Math.min(
      Math.floor((15 - estimatedTime) / 3), 
      polylineCoordinates.length - 1
    ));
    
    setCourierLocation({
      latitude: polylineCoordinates[stepIndex].latitude,
      longitude: polylineCoordinates[stepIndex].longitude,
    });

    // 当配送员到达时,更新订单状态
    if (estimatedTime <= 0 && orderStatus === 'delivering') {
      setOrderStatus('arrived');
    }
  };

  const getStatusText = () => {
    switch (orderStatus) {
      case 'confirmed':
        return '商家已确认订单';
      case 'preparing':
        return '商家正在备餐';
      case 'delivering':
        return '配送中';
      case 'arrived':
        return '已送达';
      case 'completed':
        return '订单完成';
      default:
        return '订单处理中';
    }
  };

  return (
    <View style={styles.container}>
      {/* 地图视图 */}
      <MapView
        style={styles.map}
        initialRegion={{
          latitude: (restaurantLocation.latitude + userLocation.latitude) / 2,
          longitude: (restaurantLocation.longitude + userLocation.longitude) / 2,
          latitudeDelta: 0.02,
          longitudeDelta: 0.02,
        }}
        showsUserLocation={false}
      >
        {/* 配送路线 */}
        <Polyline
          coordinates={polylineCoordinates}
          strokeColor="#FF4D4F"
          strokeWidth={4}
        />
        
        {/* 商家标记 */}
        <Marker coordinate={restaurantLocation}>
          <Image
            source={require('../../assets/images/restaurant-marker.png')}
            style={styles.markerImage}
          />
        </Marker>
        
        {/* 用户标记 */}
        <Marker coordinate={userLocation}>
          <Image
            source={require('../../assets/images/user-marker.png')}
            style={styles.markerImage}
          />
        </Marker>
        
        {/* 配送员标记 */}
        <Marker coordinate={courierLocation}>
          <Image
            source={require('../../assets/images/courier-marker.png')}
            style={styles.courierMarkerImage}
          />
        </Marker>
      </MapView>

      {/* 订单信息卡片 */}
      <View style={styles.infoCard}>
        <Text style={styles.orderStatus}>{getStatusText()}</Text>
        
        {orderStatus === 'delivering' && (
          <View style={styles.deliveryInfo}>
            <Text style={styles.estimatedTime}>预计送达时间: {estimatedTime}分钟</Text>
            
            <View style={styles.courierInfo}>
              <Image source={{ uri: order.courier.avatar }} style={styles.courierAvatar} />
              <View style={styles.courierDetails}>
                <Text style={styles.courierName}>{order.courier.name}</Text>
                <Text style={styles.courierId}>配送员 #{order.courier.id}</Text>
              </View>
              <TouchableOpacity style={styles.contactButton}>
                <Text style={styles.contactButtonText}>联系骑手</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}
        
        {orderStatus === 'arrived' && (
          <View style={styles.arrivedSection}>
            <Text style={styles.arrivedText}>您的外卖已送达楼下,请及时取餐</Text>
            <TouchableOpacity 
              style={styles.confirmButton}
              onPress={() => setOrderStatus('completed')}
            >
              <Text style={styles.confirmButtonText}>确认收货</Text>
            </TouchableOpacity>
          </View>
        )}
        
        {orderStatus === 'completed' && (
          <View style={styles.completedSection}>
            <Text style={styles.completedText}>订单已完成,感谢您的使用</Text>
            <TouchableOpacity 
              style={styles.rateButton}
              onPress={() => navigation.navigate('OrderRating', { order })}
            >
              <Text style={styles.rateButtonText}>评价订单</Text>
            </TouchableOpacity>
          </View>
        )}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    width: width,
    height: height * 0.7,
  },
  infoCard: {
    backgroundColor: 'white',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    padding: 20,
    height: height * 0.3,
    justifyContent: 'space-between',
  },
  orderStatus: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 15,
    textAlign: 'center',
  },
  deliveryInfo: {
    marginBottom: 15,
  },
  estimatedTime: {
    fontSize: 16,
    marginBottom: 15,
    color: '#666',
  },
  courierInfo: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 20,
  },
  courierAvatar: {
    width: 50,
    height: 50,
    borderRadius: 25,
  },
  courierDetails: {
    marginLeft: 15,
    flex: 1,
  },
  courierName: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  courierId: {
    fontSize: 12,
    color: '#666',
  },
  contactButton: {
    backgroundColor: '#FF4D4F',
    paddingVertical: 8,
    paddingHorizontal: 15,
    borderRadius: 20,
  },
  contactButtonText: {
    color: 'white',
    fontSize: 14,
  },
  arrivedSection: {
    alignItems: 'center',
    marginTop: 20,
  },
  arrivedText: {
    fontSize: 16,
    marginBottom: 20,
    textAlign: 'center',
  },
  confirmButton: {
    backgroundColor: '#FF4D4F',
    paddingVertical: 12,
    borderRadius: 8,
    width: '100%',
    alignItems: 'center',
  },
  confirmButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  completedSection: {
    alignItems: 'center',
    marginTop: 20,
  },
  completedText: {
    fontSize: 16,
    marginBottom: 20,
    textAlign: 'center',
  },
  rateButton: {
    backgroundColor: '#1890FF',
    paddingVertical: 12,
    borderRadius: 8,
    width: '100%',
    alignItems: 'center',
  },
  rateButtonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  markerImage: {
    width: 30,
    height: 30,
  },
  courierMarkerImage: {
    width: 40,
    height: 40,
  },
});

export default OrderTrackingScreen;

4. 性能优化

4.1 列表优化

外卖应用中会有大量的商品列表、订单列表等,使用FlatList的优化特性可以显著提升性能。

// 优化的FlatList使用示例
<FlatList
  data={goods}
  renderItem={renderGoodsItem}
  keyExtractor={item => item.id}
  getItemLayout={(data, index) => ({
    length: 100, // 每项高度
    offset: 100 * index,
    index,
  })}
  initialNumToRender={10} // 初始渲染数量
  maxToRenderPerBatch={10} // 每批次渲染数量
  windowSize={5} // 可见区域外预渲染数量
  removeClippedSubviews={true} // 移除屏幕外视图
  ListFooterComponent={loading ? renderFooter : null} // 加载更多指示器
  onEndReached={loadMoreData} // 滚动到底部加载更多
  onEndReachedThreshold={0.5} // 距离底部多远触发加载更多
  refreshing={refreshing} // 下拉刷新状态
  onRefresh={refreshData} // 下拉刷新
/>

4.2 图片优化

图片是外卖应用中占用资源较多的部分,优化图片加载可以提升应用性能和用户体验。

// 使用缓存和优化的图片组件
import React from 'react';
import { View, StyleSheet, ActivityIndicator } from 'react-native';
import FastImage from 'react-native-fast-image';

const OptimizedImage = ({ source, style, resizeMode = FastImage.resizeMode.cover }) => {
  return (
    <FastImage
      source={{
        uri: source.uri,
        priority: FastImage.priority.normal,
        cachePolicy: FastImage.cacheControl.immutable,
      }}
      style={style}
      resizeMode={resizeMode}
      onLoadStart={() => console.log('Image loading started')}
      onLoadEnd={() => console.log('Image loading finished')}
      fallbackSource={require('../assets/images/placeholder.png')}
    />
  );
};

export default OptimizedImage;

4.3 状态管理优化

合理设计Redux状态结构,避免不必要的重渲染。

// 使用reselect优化选择器
import { createSelector } from 'reselect';

// 基础选择器
const selectCart = state => state.cart;

// 创建记忆化选择器
export const selectCartItems = createSelector(
  [selectCart],
  cart => cart.items
);

export const selectCartItemsCount = createSelector(
  [selectCartItems],
  items => items.reduce((total, item) => total + item.quantity, 0)
);

export const selectCartTotal = createSelector(
  [selectCartItems],
  items => items.reduce((total, item) => total + (item.price * item.quantity), 0)
);

5. 常见问题解决方案

5.1 表单处理与验证

使用Formik和Yup处理复杂表单,实现表单验证和错误提示。

5.2 网络请求处理

封装Axios请求,处理请求拦截、响应拦截、错误处理等。

// src/api/request.js
import axios from 'axios';
import { Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

// 创建axios实例
const request = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器
request.interceptors.request.use(
  async (config) => {
    // 获取token并添加到请求头
    const token = await AsyncStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  (response) => {
    return response.data;
  },
  (error) => {
    // 错误处理
    if (error.response) {
      // 请求已发出,服务器返回状态码不在2xx范围
      const { status, data } = error.response;
      
      // 未授权,需要重新登录
      if (status === 401) {
        AsyncStorage.removeItem('token');
        // 导航到登录页面
        navigation.navigate('Login');
        Alert.alert('登录已过期,请重新登录');
      } 
      // 服务器返回错误信息
      else if (data && data.message) {
        Alert.alert('请求失败', data.message);
      } else {
        Alert.alert('请求失败', `错误码: ${status}`);
      }
    } else if (error.request) {
      // 请求已发出,但没有收到响应
      Alert.alert('网络错误', '无法连接到服务器,请检查网络');
    } else {
      // 请求配置出错
      Alert.alert('请求错误', '请求发生错误,请稍后重试');
    }
    
    return Promise.reject(error);
  }
);

export default request;

5.3 本地数据持久化

使用AsyncStorage存储用户信息、购物车数据等。

// src/utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';

// 存储数据
export const setStorageItem = async (key, value) => {
  try {
    const jsonValue = JSON.stringify(value);
    await AsyncStorage.setItem(key, jsonValue);
    return true;
  } catch (e) {
    console.error('存储数据失败:', e);
    return false;
  }
};

// 获取数据
export const getStorageItem = async (key) => {
  try {
    const jsonValue = await AsyncStorage.getItem(key);
    return jsonValue != null ? JSON.parse(jsonValue) : null;
  } catch (e) {
    console.error('获取数据失败:', e);
    return null;
  }
};

// 删除数据
export const removeStorageItem = async (key) => {
  try {
    await AsyncStorage.removeItem(key);
    return true;
  } catch (e) {
    console.error('删除数据失败:', e);
    return false;
  }
};

// 清空所有数据
export const clearStorage = async () => {
  try {
    await AsyncStorage.clear();
    return true;
  } catch (e) {
    console.error('清空数据失败:', e);
    return false;
  }
};

6. 总结与展望

6.1 项目总结

本文详细介绍了使用React Native开发外卖应用的全过程,包括项目架构设计、核心功能实现、性能优化等方面。通过学习本文,你应该能够掌握:

  • React Native核心组件(如FlatList, ScrollView, Image等)的实际应用
  • Redux状态管理在复杂应用中的使用
  • 表单处理与验证
  • 网络请求与错误处理
  • 地图服务集成与实时位置跟踪
  • 应用性能优化技巧

6.2 后续优化方向

外卖应用还有很大的优化空间,未来可以考虑以下方向:

  1. 引入TypeScript,提升代码质量和开发效率
  2. 实现离线功能,支持无网络环境下浏览历史订单
  3. 集成推送通知,及时通知用户订单状态变化
  4. 优化首屏加载速度,提升用户体验
  5. 添加单元测试和集成测试,提高代码可靠性

6.3 学习资源推荐

资源说明
React Native官方文档最权威的学习资源,包含详细的API说明和示例
React Native社区可以找到大量开源组件和解决方案
Redux官方文档学习状态管理的最佳实践
React Navigation文档掌握React Native路由管理
《React Native实战》实用的React Native开发指南

结语

外卖应用开发涉及多个方面的知识和技术,希望本文能够为你提供一个全面的指导。React Native作为跨平台开发框架,能够帮助开发者高效地构建出高质量的移动应用。通过不断学习和实践,你可以进一步提升自己的开发技能,构建出更加优秀的应用。

如果你觉得本文对你有帮助,请点赞、收藏并关注我的后续文章。如果你有任何问题或建议,欢迎在评论区留言讨论。

祝你的外卖应用开发顺利!

【免费下载链接】react-native 一个用于构建原生移动应用程序的 JavaScript 库,可以用于构建 iOS 和 Android 应用程序,支持多种原生移动平台,如 iOS,Android,React Native 等。 【免费下载链接】react-native 项目地址: https://gitcode.com/GitHub_Trending/re/react-native

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值