Skip to content

网络请求:Fetch API

React Native 进阶

在移动应用开发中,网络请求是一个非常重要的功能,它允许应用与服务器进行通信,获取数据或提交数据。React Native 内置了 Fetch API,它是一个现代的网络请求 API,提供了一种简单、灵活的方式来发送网络请求。本文将详细介绍如何在 React Native 中使用 Fetch API 进行网络请求。

1. 基本用法

发送 GET 请求

使用 Fetch API 发送 GET 请求,获取服务器数据。

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

export default function FetchGetExample() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch('https://jsonplaceholder.typicode.com/posts');
      if (!response.ok) {
        throw new Error('网络请求失败');
      }
      const jsonData = await response.json();
      setData(jsonData);
      setError(null);
    } catch (err) {
      setError(err.message);
      setData([]);
    } finally {
      setLoading(false);
    }
  };

  const renderItem = ({ item }) => (
    <View style={styles.item}>
      <Text style={styles.title}>{item.title}</Text>
      <Text style={styles.body}>{item.body}</Text>
    </View>
  );

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" color="#4CAF50" />
        <Text style={styles.loadingText}>加载中...</Text>
      </View>
    );
  }

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

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={item => item.id.toString()}
      style={styles.container}
      contentContainerStyle={styles.content}
    />
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  content: {
    padding: 16,
  },
  item: {
    backgroundColor: '#fff',
    padding: 16,
    marginBottom: 12,
    borderRadius: 8,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.1,
    shadowRadius: 3.84,
    elevation: 5,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  body: {
    fontSize: 14,
    color: '#666',
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 16,
    color: '#666',
  },
  errorText: {
    fontSize: 16,
    color: '#f44336',
  },
});

发送 POST 请求

使用 Fetch API 发送 POST 请求,向服务器提交数据。

jsx
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Alert } from 'react-native';

export default function FetchPostExample() {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [loading, setLoading] = useState(false);
  const [responseData, setResponseData] = useState(null);

  const handleSubmit = async () => {
    if (!title || !body) {
      Alert.alert('提示', '请输入标题和内容');
      return;
    }

    try {
      setLoading(true);
      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          title,
          body,
          userId: 1,
        }),
      });

      if (!response.ok) {
        throw new Error('网络请求失败');
      }

      const jsonData = await response.json();
      setResponseData(jsonData);
      Alert.alert('成功', '数据提交成功');
    } catch (err) {
      Alert.alert('错误', err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>发送 POST 请求</Text>
      
      <View style={styles.formGroup}>
        <Text style={styles.label}>标题</Text>
        <TextInput
          style={styles.input}
          value={title}
          onChangeText={setTitle}
          placeholder="请输入标题"
        />
      </View>

      <View style={styles.formGroup}>
        <Text style={styles.label}>内容</Text>
        <TextInput
          style={[styles.input, styles.textArea]}
          value={body}
          onChangeText={setBody}
          placeholder="请输入内容"
          multiline
          numberOfLines={4}
        />
      </View>

      <TouchableOpacity
        style={[styles.button, loading && styles.buttonDisabled]}
        onPress={handleSubmit}
        disabled={loading}
      >
        <Text style={styles.buttonText}>
          {loading ? '提交中...' : '提交'}
        </Text>
      </TouchableOpacity>

      {responseData && (
        <View style={styles.response}>
          <Text style={styles.responseTitle}>响应数据:</Text>
          <Text style={styles.responseText}>ID: {responseData.id}</Text>
          <Text style={styles.responseText}>标题: {responseData.title}</Text>
          <Text style={styles.responseText}>内容: {responseData.body}</Text>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  formGroup: {
    marginBottom: 16,
  },
  label: {
    fontSize: 16,
    marginBottom: 8,
    fontWeight: '500',
  },
  input: {
    backgroundColor: '#fff',
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
  },
  textArea: {
    height: 120,
    textAlignVertical: 'top',
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginTop: 16,
  },
  buttonDisabled: {
    backgroundColor: '#9e9e9e',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  response: {
    marginTop: 24,
    padding: 16,
    backgroundColor: '#e8f5e8',
    borderRadius: 8,
  },
  responseTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  responseText: {
    fontSize: 14,
    marginBottom: 4,
  },
});

2. 常用配置

设置请求头

在发送请求时设置自定义请求头。

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

export default function FetchHeadersExample() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

  const fetchWithHeaders = async () => {
    try {
      setLoading(true);
      const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', {
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer your-token-here',
        },
      });

      if (!response.ok) {
        throw new Error('网络请求失败');
      }

      const jsonData = await response.json();
      setData(jsonData);
      setError(null);
    } catch (err) {
      setError(err.message);
      setData(null);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" color="#4CAF50" />
        <Text style={styles.loadingText}>加载中...</Text>
      </View>
    );
  }

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

  return (
    <View style={styles.container}>
      <Text style={styles.title}>带请求头的 GET 请求</Text>
      <View style={styles.dataContainer}>
        <Text style={styles.dataLabel}>ID:</Text>
        <Text style={styles.dataValue}>{data.id}</Text>
      </View>
      <View style={styles.dataContainer}>
        <Text style={styles.dataLabel}>标题:</Text>
        <Text style={styles.dataValue}>{data.title}</Text>
      </View>
      <View style={styles.dataContainer}>
        <Text style={styles.dataLabel}>内容:</Text>
        <Text style={styles.dataValue}>{data.body}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 16,
    color: '#666',
  },
  errorText: {
    fontSize: 16,
    color: '#f44336',
  },
  dataContainer: {
    backgroundColor: '#fff',
    padding: 16,
    marginBottom: 12,
    borderRadius: 8,
  },
  dataLabel: {
    fontSize: 14,
    color: '#666',
    marginBottom: 4,
  },
  dataValue: {
    fontSize: 16,
    fontWeight: '500',
  },
});

处理不同的响应格式

处理 JSON、文本和 Blob 等不同的响应格式。

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

export default function FetchResponseFormatsExample() {
  const [jsonData, setJsonData] = useState(null);
  const [textData, setTextData] = useState(null);
  const [imageData, setImageData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

  const fetchDifferentFormats = async () => {
    try {
      setLoading(true);

      // 获取 JSON 数据
      const jsonResponse = await fetch('https://jsonplaceholder.typicode.com/posts/1');
      if (!jsonResponse.ok) throw new Error('JSON 请求失败');
      const json = await jsonResponse.json();
      setJsonData(json);

      // 获取文本数据
      const textResponse = await fetch('https://jsonplaceholder.typicode.com/posts/1');
      if (!textResponse.ok) throw new Error('文本请求失败');
      const text = await textResponse.text();
      setTextData(text);

      // 获取图片数据
      const imageResponse = await fetch('https://picsum.photos/200');
      if (!imageResponse.ok) throw new Error('图片请求失败');
      setImageData({ uri: 'https://picsum.photos/200' });

      setError(null);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" color="#4CAF50" />
        <Text style={styles.loadingText}>加载中...</Text>
      </View>
    );
  }

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

  return (
    <View style={styles.container}>
      <Text style={styles.title}>不同响应格式示例</Text>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>JSON 数据</Text>
        <Text style={styles.jsonText}>{JSON.stringify(jsonData, null, 2)}</Text>
      </View>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>文本数据</Text>
        <Text style={styles.textText} numberOfLines={3}>{textData}</Text>
      </View>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>图片数据</Text>
        <Image source={imageData} style={styles.image} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  section: {
    backgroundColor: '#fff',
    padding: 16,
    marginBottom: 16,
    borderRadius: 8,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '500',
    marginBottom: 12,
  },
  jsonText: {
    fontSize: 14,
    fontFamily: 'monospace',
  },
  textText: {
    fontSize: 14,
    color: '#666',
  },
  image: {
    width: 200,
    height: 200,
    borderRadius: 8,
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 16,
    color: '#666',
  },
  errorText: {
    fontSize: 16,
    color: '#f44336',
  },
});

3. 高级功能

处理超时

实现网络请求超时处理。

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

export default function FetchTimeoutExample() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

  const fetchWithTimeout = async () => {
    try {
      setLoading(true);

      // 创建一个超时 Promise
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('请求超时')), 5000); // 5秒超时
      });

      // 使用 Promise.race 实现超时控制
      const response = await Promise.race([
        fetch('https://jsonplaceholder.typicode.com/posts'),
        timeoutPromise
      ]);

      if (!response.ok) {
        throw new Error('网络请求失败');
      }

      const jsonData = await response.json();
      setData(jsonData);
      setError(null);
    } catch (err) {
      setError(err.message);
      setData(null);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" color="#4CAF50" />
        <Text style={styles.loadingText}>加载中...</Text>
      </View>
    );
  }

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

  return (
    <View style={styles.container}>
      <Text style={styles.title}>超时处理示例</Text>
      <Text style={styles.count}>获取到 {data.length} 条数据</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  count: {
    fontSize: 18,
    textAlign: 'center',
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 16,
    color: '#666',
  },
  errorText: {
    fontSize: 16,
    color: '#f44336',
  },
});

取消请求

实现网络请求的取消功能。

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

export default function FetchCancelExample() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const abortControllerRef = useRef(null);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      
      // 创建 AbortController
      abortControllerRef.current = new AbortController();
      const { signal } = abortControllerRef.current;

      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
        signal,
      });

      if (!response.ok) {
        throw new Error('网络请求失败');
      }

      const jsonData = await response.json();
      setData(jsonData);
    } catch (err) {
      if (err.name === 'AbortError') {
        setError('请求已取消');
      } else {
        setError(err.message);
      }
      setData(null);
    } finally {
      setLoading(false);
    }
  };

  const cancelFetch = () => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>取消请求示例</Text>
      
      <View style={styles.buttonContainer}>
        <TouchableOpacity
          style={[styles.button, styles.fetchButton, loading && styles.buttonDisabled]}
          onPress={fetchData}
          disabled={loading}
        >
          <Text style={styles.buttonText}>
            {loading ? '加载中...' : '开始请求'}
          </Text>
        </TouchableOpacity>
        
        <TouchableOpacity
          style={[styles.button, styles.cancelButton, !loading && styles.buttonDisabled]}
          onPress={cancelFetch}
          disabled={!loading}
        >
          <Text style={styles.buttonText}>取消请求</Text>
        </TouchableOpacity>
      </View>

      {error && (
        <View style={styles.messageContainer}>
          <Text style={styles.errorText}>{error}</Text>
        </View>
      )}

      {data && (
        <View style={styles.messageContainer}>
          <Text style={styles.successText}>成功获取到 {data.length} 条数据</Text>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    textAlign: 'center',
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 30,
  },
  button: {
    flex: 1,
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginHorizontal: 10,
  },
  fetchButton: {
    backgroundColor: '#4CAF50',
  },
  cancelButton: {
    backgroundColor: '#f44336',
  },
  buttonDisabled: {
    backgroundColor: '#9e9e9e',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  messageContainer: {
    padding: 16,
    borderRadius: 8,
    marginTop: 20,
  },
  errorText: {
    fontSize: 16,
    color: '#f44336',
  },
  successText: {
    fontSize: 16,
    color: '#4CAF50',
  },
});

并发请求

同时发送多个网络请求。

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

export default function FetchConcurrentExample() {
  const [data, setData] = useState({
    posts: null,
    comments: null,
    albums: null,
  });
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

  const fetchConcurrentData = async () => {
    try {
      setLoading(true);

      // 同时发送多个请求
      const [postsResponse, commentsResponse, albumsResponse] = await Promise.all([
        fetch('https://jsonplaceholder.typicode.com/posts'),
        fetch('https://jsonplaceholder.typicode.com/comments'),
        fetch('https://jsonplaceholder.typicode.com/albums'),
      ]);

      // 检查所有响应是否成功
      if (!postsResponse.ok || !commentsResponse.ok || !albumsResponse.ok) {
        throw new Error('网络请求失败');
      }

      // 解析所有响应
      const [posts, comments, albums] = await Promise.all([
        postsResponse.json(),
        commentsResponse.json(),
        albumsResponse.json(),
      ]);

      setData({
        posts,
        comments,
        albums,
      });
      setError(null);
    } catch (err) {
      setError(err.message);
      setData({
        posts: null,
        comments: null,
        albums: null,
      });
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" color="#4CAF50" />
        <Text style={styles.loadingText}>加载中...</Text>
      </View>
    );
  }

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

  return (
    <View style={styles.container}>
      <Text style={styles.title}>并发请求示例</Text>
      
      <View style={styles.dataContainer}>
        <Text style={styles.dataTitle}>帖子数量: {data.posts.length}</Text>
        <Text style={styles.dataTitle}>评论数量: {data.comments.length}</Text>
        <Text style={styles.dataTitle}>相册数量: {data.albums.length}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  dataContainer: {
    backgroundColor: '#fff',
    padding: 20,
    borderRadius: 8,
  },
  dataTitle: {
    fontSize: 18,
    marginBottom: 10,
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 16,
    color: '#666',
  },
  errorText: {
    fontSize: 16,
    color: '#f44336',
  },
});

4. 最佳实践

1. 封装网络请求

创建一个封装的网络请求函数,处理常见的错误和配置。

jsx
// utils/api.js
const API_BASE_URL = 'https://jsonplaceholder.typicode.com';

const fetchApi = async (endpoint, options = {}) => {
  try {
    const url = `${API_BASE_URL}${endpoint}`;
    
    const defaultOptions = {
      headers: {
        'Content-Type': 'application/json',
      },
    };

    const fetchOptions = {
      ...defaultOptions,
      ...options,
      headers: {
        ...defaultOptions.headers,
        ...options.headers,
      },
    };

    const response = await fetch(url, fetchOptions);

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

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('API request failed:', error);
    throw error;
  }
};

// 导出常用的请求方法
export const api = {
  get: (endpoint, options = {}) => fetchApi(endpoint, { ...options, method: 'GET' }),
  post: (endpoint, data, options = {}) => fetchApi(endpoint, { ...options, method: 'POST', body: JSON.stringify(data) }),
  put: (endpoint, data, options = {}) => fetchApi(endpoint, { ...options, method: 'PUT', body: JSON.stringify(data) }),
  delete: (endpoint, options = {}) => fetchApi(endpoint, { ...options, method: 'DELETE' }),
};

2. 使用状态管理

对于复杂的应用,使用状态管理库(如 Redux 或 Context API)来管理网络请求状态。

3. 错误处理

实现全面的错误处理,包括网络错误、服务器错误和业务逻辑错误。

4. 缓存策略

对于频繁访问的数据,实现合理的缓存策略,减少网络请求。

5. 加载状态

在网络请求期间显示加载状态,提高用户体验。

6. 重试机制

对于临时的网络错误,实现自动重试机制。

5. 常见问题与解决方案

问题 1:CORS 错误

问题:在开发过程中遇到 CORS 错误。

解决方案

  • 在开发环境中使用代理服务器
  • 确保服务器设置了正确的 CORS 头
  • 考虑使用 JSONP 或其他跨域解决方案

问题 2:网络请求超时

问题:网络请求超时,导致应用无响应。

解决方案

  • 实现请求超时处理
  • 设置合理的超时时间
  • 在超时后显示错误信息

问题 3:数据解析错误

问题:服务器返回的数据格式不正确,导致解析错误。

解决方案

  • 检查服务器返回的数据格式
  • 实现错误处理,捕获解析错误
  • 与后端开发人员协调,确保数据格式一致

问题 4:认证失败

问题:需要认证的请求失败。

解决方案

  • 确保正确设置认证头
  • 实现 token 刷新机制
  • 处理认证错误,引导用户重新登录

6. 总结

Fetch API 是 React Native 中内置的网络请求 API,它提供了一种现代、灵活的方式来发送网络请求。通过本文的学习,你应该掌握了以下内容:

  1. 使用 Fetch API 发送 GET 和 POST 请求
  2. 设置请求头和处理不同的响应格式
  3. 实现超时处理和请求取消
  4. 发送并发请求
  5. 封装网络请求和最佳实践
  6. 常见问题的解决方案

在实际开发中,合理使用 Fetch API 可以创建出更加可靠、高效的网络请求功能。通过结合其他技术,如状态管理和缓存策略,可以创建出更加专业、用户友好的应用。

© 2026 编程马·菜鸟教程 版权所有