ReactNative Expo 详解

ReactNative Expo 详解:从入门到实践

您好!作为一名资深移动应用开发专家,很高兴能为您详细解读 ReactNative Expo

ReactNative Expo 是一个基于 React Native 的开源工具链和平台,旨在简化移动应用的开发、构建、部署流程。它封装了许多复杂的原生模块和配置,让开发者可以用纯 JavaScript/TypeScript 编写跨平台的原生应用,而无需深入了解原生的构建系统(如 Xcode 或 Android Studio)。

Expo 是什么?

简单来说,Expo 提供了一套强大的工具和预构建的原生模块集合,让您能够:

graph TD A[开发者]-->B[Expo CLI / SDK] B-->C{开发体验优化} C-->D[即时预览 Expo Go] C-->E[预构建模块] C-->F[云构建服务] D-->G[iOS / Android 设备] E-->H[相机 / 位置 / 通知等原生功能] F-->I[App Store / Google Play] I-->J[最终用户] H--简化接入-->B J--下载-->I

开发原生应用是否友好?

答案是:非常友好,尤其对于希望快速迭代、降低原生开发门槛的团队和个人。

Expo 的友好性体现在以下几个方面:

然而,也有一些限制和权衡

关键概念区分:

有哪些框架支持吗?

Expo 自身就是基于 React Native 的,因此它继承了 React Native 的所有生态系统,并在此基础上提供了更便利的工具和库。

主要支持和常用的框架/库包括:

  1. React Native 核心组件: View, Text, Image, ScrollView, Button, TextInput 等。
  2. React 状态管理:
    • Redux / Redux Toolkit: 全局状态管理的首选。
    • Zustand / Jotai: 轻量级和现代化的状态管理库。
    • Context API / useReducer: React 内置的状态管理方案。
  3. 导航:
    • React Navigation: 官方推荐的导航解决方案,支持栈导航、抽屉导航、Tab 导航等。
  4. UI 组件库:
    • React Native Paper: Material Design 风格的组件库。
    • NativeBase: 另一个流行的跨平台组件库。
    • TBD (Tailwind CSS for React Native): 结合 Tailwind CSS 进行样式开发。
  5. 数据请求:
    • Axios / Fetch API: 最常用的数据请求库。
    • React Query / SWR: 强大的数据缓存和同步库。
  6. Expo SDK 模块: Expo 自身提供大量预构建模块,例如:
    • `expo-camera`: 摄像头
    • `expo-location`: 地理位置
    • `expo-image-picker`: 图片选择器
    • `expo-notifications`: 推送通知
    • `expo-contacts`: 通讯录
    • `expo-file-system`: 文件系统
    • `expo-av`: 音频/视频播放

对接支付是否方便?

对接支付在 Expo(特别是 Managed Workflow)中可能会比在 Bare Workflow 或纯原生开发中略复杂一些,但总体来说是可行的

常见的支付对接方式:

  1. Webview 支付(Expo Managed Workflow 兼容性最好):

    这是在托管工作流中最推荐的方式。通过 `expo-web-browser` 或 `react-native-webview` (需要 EAS Build 或 Bare Workflow) 打开支付服务提供商(如 Stripe Checkout, PayPal Checkout, 微信支付/支付宝 H5 支付页)的 Web 页面进行支付。

    用户完成支付后,支付服务商会重定向回您的应用定义的回调 URL(Deep Link),您可以监听这个 URL 来获取支付结果。

    
    import * as WebBrowser from 'expo-web-browser';
    
    const handlePayment = async (paymentUrl) => {
      let result = await WebBrowser.openBrowserAsync(paymentUrl);
      // result 包含支付完成后的信息,例如 URL
      console.log(result);
      // 需要配置 Deep Linking 来处理支付回调
    };
    
    // 示例调用:
    // handlePayment('https://your-payment-provider.com/checkout?orderId=123');
                    
  2. 第三方原生 SDK 包装器(需要 EAS Build 或 Bare Workflow):

    许多支付服务商(如 Stripe、PayPal、Apple Pay、Google Pay、Adyen 等)都提供原生的 SDK。在 React Native 生态中,通常会有第三方社区包将这些原生 SDK 包装成 JS 模块,例如 `tipsi-stripe` (Stripe)、`react-native-paypal` 等。

    如果您的项目处于 Managed Workflow,您将需要使用 EAS Build 来构建,并在 `app.json` 或 `app.config.js` 中配置插件以包含这些原生依赖,或者将项目弹出到 Bare Workflow。

    这种方式提供更原生的支付体验,但配置和集成更为复杂。

  3. Apple Pay / Google Pay (需要 EAS Build 或 Bare Workflow):

    Expo 提供了 `expo-payments-stripe` (已废弃,建议直接使用 Stripe 官方的 `@{stripe/stripe-react-native}`),但通常更推荐直接使用 `@{stripe/stripe-react-native}` 这样的第三方库来集成 Apple Pay 和 Google Pay。

    这些库通常需要链接原生模块,因此也需要 EAS Build 或 Bare Workflow。

  4. 应用内购买 (IAP - In-App Purchase):

    `expo-in-app-purchases` 模块支持 App Store 和 Google Play 的应用内购买功能。这主要用于销售虚拟商品、订阅等。

    
    import * as InAppPurchases from 'expo-in-app-purchases';
    
    const getProducts = async () => {
      const { results } = await InAppPurchases.getProductsAsync(['your_product_id']);
      console.log(results);
    };
    
    // ... 更多功能如购买、查询购买记录等
                    

总结支付对接:

对于 Expo Managed Workflow,最简单和推荐的是使用 Webview 支付

如果需要更原生、更深度的支付集成(如直接调用支付SDK、Apple Pay/Google Pay),您需要使用 EAS Build (Expo Application Services Build) 来集成自定义的原生模块,或者切换到 Bare Workflow

可以开发哪些小示例快速体验开发流程?

以下是一些适合快速上手 Expo 开发流程的小示例,可以帮助您体验其核心功能:

1. 待办事项列表 (Todo App)

目的: 体验组件、状态管理、列表渲染、基本样式。

核心功能:

技术点: `useState` 钩子管理待办列表,`FlatList` 渲染列表,`TouchableOpacity` 用于交互。


// App.js
import React, { useState } from 'react';
import { StyleSheet, Text, View, TextInput, Button, FlatList, TouchableOpacity } from 'react-native';

export default function App() {
  const [todo, setTodo] = useState('');
  const [todos, setTodos] = useState([]);

  const addTodo = () => {
    if (todo.length > 0) {
      setTodos(currentTodos => [
        ...currentTodos,
        { key: Math.random().toString(), text: todo, completed: false }
      ]);
      setTodo('');
    }
  };

  const toggleTodo = (key) => {
    setTodos(currentTodos =>
      currentTodos.map(item =>
        item.key === key ? { ...item, completed: !item.completed } : item
      )
    );
  };

  const deleteTodo = (key) => {
    setTodos(currentTodos => currentTodos.filter(item => item.key !== key));
  };

  return (
    
      我的待办事项
      
        
        

2. 天气应用 (Weather App)

目的: 体验数据请求 (API)、地理位置、条件渲染。

核心功能:

技术点: `expo-location` 获取位置,`fetch` 或 `axios` 调用 API,`useEffect` 处理副作用。


// App.js
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, ActivityIndicator, Image } from 'react-native';
import * as Location from 'expo-location';
import axios from 'axios';

const API_KEY = 'YOUR_OPENWEATHERMAP_API_KEY'; // 获取您自己的 API Key

export default function WeatherApp() {
  const [location, setLocation] = useState(null);
  const [weather, setWeather] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    (async () => {
      let { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        setErrorMsg('需要地理位置权限才能获取天气');
        setLoading(false);
        return;
      }

      let locationResult = await Location.getCurrentPositionAsync({});
      setLocation(locationResult);
      fetchWeather(locationResult.coords.latitude, locationResult.coords.longitude);
    })();
  }, []);

  const fetchWeather = async (latitude, longitude) => {
    try {
      const response = await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric&lang=zh_cn`
      );
      setWeather(response.data);
    } catch (error) {
      setErrorMsg('获取天气失败,请检查API Key或网络');
      console.error(error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      
        
        正在获取地理位置和天气...
      
    );
  }

  if (errorMsg) {
    return (
      
        {errorMsg}
      
    );
  }

  if (!weather) {
    return (
      
        未能获取天气信息。
      
    );
  }

  const iconUrl = `https://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`;

  return (
    
      {weather.name}
      
      {Math.round(weather.main.temp)}°C
      {weather.weather[0].description}
    
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#add8e6', // 淡蓝色背景
  },
  centered: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  errorText: {
    color: 'red',
    fontSize: 16,
  },
  city: {
    fontSize: 48,
    fontWeight: 'bold',
    color: '#fff',
    marginBottom: 10,
  },
  weatherIcon: {
    width: 150,
    height: 150,
  },
  temperature: {
    fontSize: 80,
    fontWeight: '200',
    color: '#fff',
  },
  description: {
    fontSize: 24,
    color: '#fff',
    textTransform: 'capitalize',
    marginTop: 10,
  },
});
                

重要提示: 在运行 Weather App 示例前,请务必前往 OpenWeatherMap 注册并获取您自己的 API Key,替换示例代码中的 `YOUR_OPENWEATHERMAP_API_KEY`。

3. 图片选择器与显示 (Image Picker & Display)

目的: 体验 Expo SDK 模块(`expo-image-picker`),权限管理。

核心功能:

技术点: `expo-image-picker` 模块,`useState` 存储图片 URI,`Button` 和 `Image` 组件。


// App.js
import React, { useState } from 'react';
import { StyleSheet, Text, View, Image, Button } from 'react-native';
import * as ImagePicker from 'expo-image-picker';

export default function ImagePickerApp() {
  const [selectedImage, setSelectedImage] = useState(null);

  const pickImage = async () => {
    // 请求媒体库权限
    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
    if (status !== 'granted') {
      alert('抱歉,我们需要媒体库权限才能工作!');
      return;
    }

    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.canceled) {
      // 在 Expo Go (Managed Workflow) 中, assets 数组包含 uri 属性
      // 在其他运行环境中,可能需要访问 result.uri
      setSelectedImage(result.assets[0].uri);
    }
  };

  const takePhoto = async () => {
    // 请求相机权限
    const { status } = await ImagePicker.requestCameraPermissionsAsync();
    if (status !== 'granted') {
      alert('抱歉,我们需要相机权限才能工作!');
      return;
    }

    let result = await ImagePicker.launchCameraAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.canceled) {
      setSelectedImage(result.assets[0].uri);
    }
  };

  return (
    
      图片选择与显示
      
        

如何开始这些示例?

  1. 安装 Expo CLI:
    npm install -g expo-cli
  2. 创建新项目:
    expo init MyAwesomeApp
    选择 Blank template (TypeScript 或 JavaScript 均可)。
  3. 进入项目目录并运行:
    cd MyAwesomeApp
    npm start
  4. 下载 Expo Go App: 在您的 iOS 或 Android 设备上下载并安装 Expo Go 应用。
  5. 扫描二维码: 使用 Expo Go App 扫描命令行终端或浏览器中显示的二维码,您的应用就会在设备上加载。
  6. 替换代码: 将上述任一示例的代码替换 `App.js` 文件中的内容即可。

通过这些小示例,您应该能对 Expo 的开发流程有一个直观且快速的体验。祝您开发顺利!

互动区域

登录后可以点赞此内容

参与互动

登录后可以点赞和评论此内容,与作者互动交流