expo-modules

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Expo Modules

Expo 模块

Use this skill when working with Expo's extensive SDK modules for accessing device features and native functionality.
当你需要使用Expo丰富的SDK模块来访问设备功能与原生特性时,可以运用此技能。

Key Concepts

核心概念

Camera

相机

tsx
import { Camera, CameraType } from 'expo-camera';
import { useState } from 'react';
import { Button, View } from 'react-native';

export default function CameraScreen() {
  const [permission, requestPermission] = Camera.useCameraPermissions();
  const [type, setType] = useState(CameraType.back);

  if (!permission?.granted) {
    return (
      <View>
        <Button title="Grant Permission" onPress={requestPermission} />
      </View>
    );
  }

  return (
    <Camera style={{ flex: 1 }} type={type}>
      <Button
        title="Flip Camera"
        onPress={() =>
          setType(type === CameraType.back ? CameraType.front : CameraType.back)
        }
      />
    </Camera>
  );
}
tsx
import { Camera, CameraType } from 'expo-camera';
import { useState } from 'react';
import { Button, View } from 'react-native';

export default function CameraScreen() {
  const [permission, requestPermission] = Camera.useCameraPermissions();
  const [type, setType] = useState(CameraType.back);

  if (!permission?.granted) {
    return (
      <View>
        <Button title="Grant Permission" onPress={requestPermission} />
      </View>
    );
  }

  return (
    <Camera style={{ flex: 1 }} type={type}>
      <Button
        title="Flip Camera"
        onPress={() =>
          setType(type === CameraType.back ? CameraType.front : CameraType.back)
        }
      />
    </Camera>
  );
}

Location

位置

tsx
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';

export function useLocation() {
  const [location, setLocation] = useState<Location.LocationObject | null>(null);

  useEffect(() => {
    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') return;

      const loc = await Location.getCurrentPositionAsync({});
      setLocation(loc);
    })();
  }, []);

  return location;
}
tsx
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';

export function useLocation() {
  const [location, setLocation] = useState<Location.LocationObject | null>(null);

  useEffect(() => {
    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') return;

      const loc = await Location.getCurrentPositionAsync({});
      setLocation(loc);
    })();
  }, []);

  return location;
}

Notifications

通知

tsx
import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export function useNotifications() {
  useEffect(() => {
    const subscription = Notifications.addNotificationReceivedListener(
      (notification) => {
        console.log(notification);
      }
    );

    return () => subscription.remove();
  }, []);

  const sendNotification = async () => {
    await Notifications.scheduleNotificationAsync({
      content: {
        title: 'Hello!',
        body: 'This is a notification',
      },
      trigger: { seconds: 2 },
    });
  };

  return { sendNotification };
}
tsx
import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export function useNotifications() {
  useEffect(() => {
    const subscription = Notifications.addNotificationReceivedListener(
      (notification) => {
        console.log(notification);
      }
    );

    return () => subscription.remove();
  }, []);

  const sendNotification = async () => {
    await Notifications.scheduleNotificationAsync({
      content: {
        title: 'Hello!',
        body: 'This is a notification',
      },
      trigger: { seconds: 2 },
    });
  };

  return { sendNotification };
}

File System

文件系统

tsx
import * as FileSystem from 'expo-file-system';

export async function saveFile(data: string, filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  await FileSystem.writeAsStringAsync(uri, data);
  return uri;
}

export async function readFile(filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  const content = await FileSystem.readAsStringAsync(uri);
  return content;
}

export async function downloadFile(url: string, filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  const download = await FileSystem.downloadAsync(url, uri);
  return download.uri;
}
tsx
import * as FileSystem from 'expo-file-system';

export async function saveFile(data: string, filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  await FileSystem.writeAsStringAsync(uri, data);
  return uri;
}

export async function readFile(filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  const content = await FileSystem.readAsStringAsync(uri);
  return content;
}

export async function downloadFile(url: string, filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  const download = await FileSystem.downloadAsync(url, uri);
  return download.uri;
}

Image Picker

图片选择器

tsx
import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { Button, Image, View } from 'react-native';

export default function ImagePickerScreen() {
  const [image, setImage] = useState<string | null>(null);

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

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

  return (
    <View>
      <Button title="Pick Image" onPress={pickImage} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
    </View>
  );
}
tsx
import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { Button, Image, View } from 'react-native';

export default function ImagePickerScreen() {
  const [image, setImage] = useState<string | null>(null);

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

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

  return (
    <View>
      <Button title="Pick Image" onPress={pickImage} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
    </View>
  );
}

Best Practices

最佳实践

Permission Handling

权限处理

tsx
import * as Location from 'expo-location';

async function requestLocationPermission() {
  const { status: foregroundStatus } =
    await Location.requestForegroundPermissionsAsync();

  if (foregroundStatus !== 'granted') {
    console.log('Permission denied');
    return false;
  }

  // Request background permission only if needed
  const { status: backgroundStatus } =
    await Location.requestBackgroundPermissionsAsync();

  return backgroundStatus === 'granted';
}
tsx
import * as Location from 'expo-location';

async function requestLocationPermission() {
  const { status: foregroundStatus } =
    await Location.requestForegroundPermissionsAsync();

  if (foregroundStatus !== 'granted') {
    console.log('Permission denied');
    return false;
  }

  // Request background permission only if needed
  const { status: backgroundStatus } =
    await Location.requestBackgroundPermissionsAsync();

  return backgroundStatus === 'granted';
}

Secure Storage

安全存储

tsx
import * as SecureStore from 'expo-secure-store';

export async function saveToken(key: string, value: string) {
  await SecureStore.setItemAsync(key, value);
}

export async function getToken(key: string) {
  return await SecureStore.getItemAsync(key);
}

export async function deleteToken(key: string) {
  await SecureStore.deleteItemAsync(key);
}
tsx
import * as SecureStore from 'expo-secure-store';

export async function saveToken(key: string, value: string) {
  await SecureStore.setItemAsync(key, value);
}

export async function getToken(key: string) {
  return await SecureStore.getItemAsync(key);
}

export async function deleteToken(key: string) {
  await SecureStore.deleteItemAsync(key);
}

Device Info

设备信息

tsx
import * as Device from 'expo-device';
import * as Application from 'expo-application';
import Constants from 'expo-constants';

export function getDeviceInfo() {
  return {
    deviceName: Device.deviceName,
    deviceType: Device.deviceType,
    osName: Device.osName,
    osVersion: Device.osVersion,
    appVersion: Application.nativeApplicationVersion,
    buildVersion: Application.nativeBuildVersion,
    expoVersion: Constants.expoVersion,
  };
}
tsx
import * as Device from 'expo-device';
import * as Application from 'expo-application';
import Constants from 'expo-constants';

export function getDeviceInfo() {
  return {
    deviceName: Device.deviceName,
    deviceType: Device.deviceType,
    osName: Device.osName,
    osVersion: Device.osVersion,
    appVersion: Application.nativeApplicationVersion,
    buildVersion: Application.nativeBuildVersion,
    expoVersion: Constants.expoVersion,
  };
}

Common Patterns

通用模式

Persistent Storage

持久化存储

tsx
import AsyncStorage from '@react-native-async-storage/async-storage';

export const storage = {
  async set(key: string, value: any) {
    await AsyncStorage.setItem(key, JSON.stringify(value));
  },

  async get<T>(key: string): Promise<T | null> {
    const item = await AsyncStorage.getItem(key);
    return item ? JSON.parse(item) : null;
  },

  async remove(key: string) {
    await AsyncStorage.removeItem(key);
  },

  async clear() {
    await AsyncStorage.clear();
  },
};
tsx
import AsyncStorage from '@react-native-async-storage/async-storage';

export const storage = {
  async set(key: string, value: any) {
    await AsyncStorage.setItem(key, JSON.stringify(value));
  },

  async get<T>(key: string): Promise<T | null> {
    const item = await AsyncStorage.getItem(key);
    return item ? JSON.parse(item) : null;
  },

  async remove(key: string) {
    await AsyncStorage.removeItem(key);
  },

  async clear() {
    await AsyncStorage.clear();
  },
};

Background Tasks

后台任务

tsx
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';

const BACKGROUND_FETCH_TASK = 'background-fetch';

TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
  // Do work here
  console.log('Background task running');
  return BackgroundFetch.BackgroundFetchResult.NewData;
});

export async function registerBackgroundTask() {
  return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
    minimumInterval: 60 * 15, // 15 minutes
    stopOnTerminate: false,
    startOnBoot: true,
  });
}
tsx
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';

const BACKGROUND_FETCH_TASK = 'background-fetch';

TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
  // Do work here
  console.log('Background task running');
  return BackgroundFetch.BackgroundFetchResult.NewData;
});

export async function registerBackgroundTask() {
  return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
    minimumInterval: 60 * 15, // 15 minutes
    stopOnTerminate: false,
    startOnBoot: true,
  });
}

Sharing Content

内容分享

tsx
import * as Sharing from 'expo-sharing';

export async function shareContent(uri: string) {
  const isAvailable = await Sharing.isAvailableAsync();

  if (!isAvailable) {
    console.log('Sharing is not available');
    return;
  }

  await Sharing.shareAsync(uri, {
    mimeType: 'image/jpeg',
    dialogTitle: 'Share this image',
  });
}
tsx
import * as Sharing from 'expo-sharing';

export async function shareContent(uri: string) {
  const isAvailable = await Sharing.isAvailableAsync();

  if (!isAvailable) {
    console.log('Sharing is not available');
    return;
  }

  await Sharing.shareAsync(uri, {
    mimeType: 'image/jpeg',
    dialogTitle: 'Share this image',
  });
}

Related Skills

相关技能

  • expo-config: Configuring module permissions
  • expo-build: Including modules in builds
  • expo-config: 配置模块权限
  • expo-build: 在构建中集成模块