Appearance
网络请求: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,它提供了一种现代、灵活的方式来发送网络请求。通过本文的学习,你应该掌握了以下内容:
- 使用 Fetch API 发送 GET 和 POST 请求
- 设置请求头和处理不同的响应格式
- 实现超时处理和请求取消
- 发送并发请求
- 封装网络请求和最佳实践
- 常见问题的解决方案
在实际开发中,合理使用 Fetch API 可以创建出更加可靠、高效的网络请求功能。通过结合其他技术,如状态管理和缓存策略,可以创建出更加专业、用户友好的应用。
