React Native网络请求 #

概述 #

React Native 提供了与 Web 一致的网络 API,可以直接使用 Fetch API 或第三方库如 Axios 进行网络请求。

Fetch API #

基本使用 #

tsx
import React, {useState, useEffect} from 'react';
import {View, Text, ActivityIndicator, StyleSheet} from 'react-native';

interface User {
  id: number;
  name: string;
  email: string;
}

const FetchExample = () => {
  const [data, setData] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetchUser();
  }, []);

  const fetchUser = async () => {
    try {
      setLoading(true);
      const response = await fetch('https://jsonplaceholder.typicode.com/users/1');

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const json: User = await response.json();
      setData(json);
      setError(null);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      <View style={styles.container}>
        <ActivityIndicator size="large" color="#007AFF" />
      </View>
    );
  }

  if (error) {
    return (
      <View style={styles.container}>
        <Text style={styles.error}>{error}</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.name}>{data?.name}</Text>
      <Text style={styles.email}>{data?.email}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 16,
  },
  name: {
    fontSize: 24,
    fontWeight: 'bold',
  },
  email: {
    fontSize: 16,
    color: '#666',
    marginTop: 8,
  },
  error: {
    fontSize: 16,
    color: '#FF3B30',
  },
});

export default FetchExample;

POST 请求 #

tsx
const createUser = async (userData: {name: string; email: string}) => {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData),
    });

    if (!response.ok) {
      throw new Error('Failed to create user');
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error creating user:', error);
    throw error;
  }
};

带认证的请求 #

tsx
const fetchWithAuth = async (url: string, token: string) => {
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  });

  if (response.status === 401) {
    // Token 过期,刷新或跳转登录
    throw new Error('Unauthorized');
  }

  return response.json();
};

Axios #

Axios 是一个流行的 HTTP 客户端,提供了更友好的 API 和更多功能。

安装 #

bash
npm install axios

基本配置 #

tsx
import axios from 'axios';

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

export default api;

请求拦截器 #

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

const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
});

api.interceptors.request.use(
  async config => {
    const token = await AsyncStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  },
);

api.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      // Token 过期处理
      await AsyncStorage.removeItem('token');
    }
    return Promise.reject(error);
  },
);

export default api;

使用 Axios #

tsx
import React, {useState, useEffect} from 'react';
import {View, Text, FlatList, ActivityIndicator, StyleSheet} from 'react-native';
import api from './api';

interface Post {
  id: number;
  title: string;
  body: string;
}

const AxiosExample = () => {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetchPosts();
  }, []);

  const fetchPosts = async () => {
    try {
      setLoading(true);
      const response = await api.get<Post[]>('/posts');
      setPosts(response.data);
      setError(null);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      <View style={styles.container}>
        <ActivityIndicator size="large" color="#007AFF" />
      </View>
    );
  }

  if (error) {
    return (
      <View style={styles.container}>
        <Text style={styles.error}>{error}</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={posts}
      keyExtractor={item => item.id.toString()}
      renderItem={({item}) => (
        <View style={styles.item}>
          <Text style={styles.title}>{item.title}</Text>
          <Text numberOfLines={2}>{item.body}</Text>
        </View>
      )}
      contentContainerStyle={styles.list}
    />
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  list: {
    padding: 16,
  },
  item: {
    backgroundColor: '#fff',
    padding: 16,
    marginBottom: 12,
    borderRadius: 8,
    shadowColor: '#000',
    shadowOffset: {width: 0, height: 2},
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  title: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  error: {
    fontSize: 16,
    color: '#FF3B30',
  },
});

export default AxiosExample;

POST/PUT/DELETE #

tsx
const createPost = async (post: {title: string; body: string; userId: number}) => {
  const response = await api.post('/posts', post);
  return response.data;
};

const updatePost = async (id: number, post: Partial<Post>) => {
  const response = await api.put(`/posts/${id}`, post);
  return response.data;
};

const deletePost = async (id: number) => {
  await api.delete(`/posts/${id}`);
};

文件上传 #

上传图片 #

tsx
import {launchImageLibrary} from 'react-native-image-picker';

const uploadImage = async () => {
  const result = await launchImageLibrary({
    mediaType: 'photo',
    quality: 0.8,
  });

  if (result.didCancel || !result.assets?.[0]) {
    return;
  }

  const asset = result.assets[0];
  const formData = new FormData();

  formData.append('image', {
    uri: asset.uri,
    type: asset.type,
    name: asset.fileName,
  });

  try {
    const response = await api.post('/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
      onUploadProgress: progressEvent => {
        const percentCompleted = Math.round(
          (progressEvent.loaded * 100) / (progressEvent.total || 1),
        );
        console.log(`Upload progress: ${percentCompleted}%`);
      },
    });
    return response.data;
  } catch (error) {
    console.error('Upload error:', error);
    throw error;
  }
};

上传进度 #

tsx
const uploadWithProgress = async (file: FormData, onProgress: (percent: number) => void) => {
  try {
    const response = await api.post('/upload', file, {
      onUploadProgress: progressEvent => {
        const percent = Math.round(
          (progressEvent.loaded * 100) / (progressEvent.total || 1),
        );
        onProgress(percent);
      },
    });
    return response.data;
  } catch (error) {
    throw error;
  }
};

文件下载 #

tsx
import RNFS from 'react-native-fs';

const downloadFile = async (url: string, filename: string) => {
  const path = `${RNFS.DocumentDirectoryPath}/${filename}`;

  try {
    const result = await RNFS.downloadFile({
      fromUrl: url,
      toFile: path,
      progress: res => {
        const percent = Math.round(
          (res.bytesWritten / res.contentLength) * 100,
        );
        console.log(`Download progress: ${percent}%`);
      },
      progressDivider: 10,
    }).promise;

    console.log('File downloaded to:', path);
    return path;
  } catch (error) {
    console.error('Download error:', error);
    throw error;
  }
};

错误处理 #

统一错误处理 #

tsx
interface ApiError {
  message: string;
  code: string;
  status: number;
}

const handleApiError = (error: unknown): ApiError => {
  if (axios.isAxiosError(error)) {
    const status = error.response?.status || 0;
    const message = error.response?.data?.message || error.message;

    switch (status) {
      case 400:
        return {message: '请求参数错误', code: 'BAD_REQUEST', status};
      case 401:
        return {message: '未授权,请登录', code: 'UNAUTHORIZED', status};
      case 403:
        return {message: '拒绝访问', code: 'FORBIDDEN', status};
      case 404:
        return {message: '资源不存在', code: 'NOT_FOUND', status};
      case 500:
        return {message: '服务器错误', code: 'SERVER_ERROR', status};
      default:
        return {message, code: 'UNKNOWN', status};
    }
  }

  return {
    message: error instanceof Error ? error.message : '未知错误',
    code: 'UNKNOWN',
    status: 0,
  };
};

// 使用
const fetchData = async () => {
  try {
    const response = await api.get('/data');
    return response.data;
  } catch (error) {
    const apiError = handleApiError(error);
    console.error('API Error:', apiError);
    throw apiError;
  }
};

请求取消 #

tsx
import axios, {CancelTokenSource} from 'axios';

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const cancelTokenRef = useRef<CancelTokenSource | null>(null);

  useEffect(() => {
    if (query.length < 2) {
      setResults([]);
      return;
    }

    const search = async () => {
      if (cancelTokenRef.current) {
        cancelTokenRef.current.cancel('Cancelled due to new request');
      }

      cancelTokenRef.current = axios.CancelToken.source();

      try {
        const response = await api.get('/search', {
          params: {q: query},
          cancelToken: cancelTokenRef.current.token,
        });
        setResults(response.data);
      } catch (error) {
        if (!axios.isCancel(error)) {
          console.error('Search error:', error);
        }
      }
    };

    search();

    return () => {
      if (cancelTokenRef.current) {
        cancelTokenRef.current.cancel('Component unmounted');
      }
    };
  }, [query]);

  return (
    <View>
      <TextInput
        value={query}
        onChangeText={setQuery}
        placeholder="Search..."
      />
      <FlatList
        data={results}
        keyExtractor={item => item.id}
        renderItem={({item}) => <Text>{item.name}</Text>}
      />
    </View>
  );
};

离线处理 #

检测网络状态 #

tsx
import NetInfo from '@react-native-community/netinfo';

const checkNetwork = async (): Promise<boolean> => {
  const state = await NetInfo.fetch();
  return state.isConnected ?? false;
};

// 监听网络变化
NetInfo.addEventListener(state => {
  console.log('Connection type:', state.type);
  console.log('Is connected:', state.isConnected);
});

离线队列 #

tsx
interface QueuedRequest {
  id: string;
  url: string;
  method: string;
  data?: any;
  timestamp: number;
}

const offlineQueue = {
  async add(request: Omit<QueuedRequest, 'id' | 'timestamp'>) {
    const queue = await this.getAll();
    const newRequest: QueuedRequest = {
      ...request,
      id: Date.now().toString(),
      timestamp: Date.now(),
    };
    queue.push(newRequest);
    await AsyncStorage.setItem('offline_queue', JSON.stringify(queue));
  },

  async getAll(): Promise<QueuedRequest[]> {
    const data = await AsyncStorage.getItem('offline_queue');
    return data ? JSON.parse(data) : [];
  },

  async remove(id: string) {
    const queue = await this.getAll();
    const filtered = queue.filter(item => item.id !== id);
    await AsyncStorage.setItem('offline_queue', JSON.stringify(filtered));
  },

  async processQueue() {
    const queue = await this.getAll();
    const isConnected = await checkNetwork();

    if (!isConnected) return;

    for (const request of queue) {
      try {
        await api.request({
          url: request.url,
          method: request.method,
          data: request.data,
        });
        await this.remove(request.id);
      } catch (error) {
        console.error('Failed to process request:', request.id);
      }
    }
  },
};

总结 #

React Native 网络请求要点:

  • Fetch API:内置支持,简单直接
  • Axios:功能丰富,推荐使用
  • 拦截器:统一处理认证和错误
  • 文件上传:使用 FormData
  • 请求取消:避免内存泄漏
  • 离线处理:检测网络状态,队列重试

继续学习 相机与相册,了解设备功能集成。

最后更新:2026-03-28