Appearance
GET / POST / 上传文件
第六部分:数据请求与状态管理
在 React Native 应用中,网络请求是常见的功能,包括获取数据、提交数据和上传文件等。本文将详细介绍如何使用 Fetch API 和 Axios 实现各种类型的网络请求。
1. GET 请求
使用 Fetch API
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator } from 'react-native';
export default function GetRequestExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error('网络请求失败');
}
const json = await response.json();
setData(json);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#4CAF50" />
</View>
);
}
if (error) {
return (
<View style={styles.container}>
<Text style={styles.errorText}>错误: {error}</Text>
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.body}>{item.body}</Text>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
},
});使用 Axios
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator } from 'react-native';
import axios from 'axios';
export default function GetRequestAxiosExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setData(response.data);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#4CAF50" />
</View>
);
}
if (error) {
return (
<View style={styles.container}>
<Text style={styles.errorText}>错误: {error}</Text>
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.body}>{item.body}</Text>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
},
});2. POST 请求
使用 Fetch API
jsx
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
export default function PostRequestExample() {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
if (!title || !body) {
Alert.alert('错误', '请填写标题和内容');
return;
}
setLoading(true);
try {
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 json = await response.json();
Alert.alert('成功', `提交成功!ID: ${json.id}`);
} catch (error) {
Alert.alert('错误', error.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
value={title}
onChangeText={setTitle}
placeholder="标题"
marginBottom={10}
/>
<TextInput
style={[styles.input, styles.textArea]}
value={body}
onChangeText={setBody}
placeholder="内容"
multiline
numberOfLines={4}
marginBottom={20}
/>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleSubmit}
disabled={loading}
>
<Text style={styles.buttonText}>{loading ? '提交中...' : '提交'}</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
},
textArea: {
height: 100,
textAlignVertical: 'top',
},
button: {
backgroundColor: '#4CAF50',
padding: 15,
borderRadius: 5,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
});使用 Axios
jsx
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import axios from 'axios';
export default function PostRequestAxiosExample() {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
if (!title || !body) {
Alert.alert('错误', '请填写标题和内容');
return;
}
setLoading(true);
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', {
title,
body,
userId: 1,
});
Alert.alert('成功', `提交成功!ID: ${response.data.id}`);
} catch (error) {
Alert.alert('错误', error.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
value={title}
onChangeText={setTitle}
placeholder="标题"
marginBottom={10}
/>
<TextInput
style={[styles.input, styles.textArea]}
value={body}
onChangeText={setBody}
placeholder="内容"
multiline
numberOfLines={4}
marginBottom={20}
/>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleSubmit}
disabled={loading}
>
<Text style={styles.buttonText}>{loading ? '提交中...' : '提交'}</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
},
textArea: {
height: 100,
textAlignVertical: 'top',
},
button: {
backgroundColor: '#4CAF50',
padding: 15,
borderRadius: 5,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
});3. 上传文件
使用 Fetch API
jsx
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Image } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function FileUploadExample() {
const [image, setImage] = useState(null);
const [loading, setLoading] = useState(false);
const pickImage = async () => {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
Alert.alert('权限错误', '需要相册权限才能选择图片');
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
setImage(result.assets[0]);
}
};
const uploadImage = async () => {
if (!image) {
Alert.alert('错误', '请先选择图片');
return;
}
setLoading(true);
try {
const formData = new FormData();
formData.append('image', {
uri: image.uri,
type: 'image/jpeg',
name: 'photo.jpg',
});
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: formData,
});
if (!response.ok) {
throw new Error('上传失败');
}
const json = await response.json();
Alert.alert('成功', '图片上传成功!');
} catch (error) {
Alert.alert('错误', error.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.pickButton} onPress={pickImage}>
<Text style={styles.pickButtonText}>选择图片</Text>
</TouchableOpacity>
{image && (
<View style={styles.imageContainer}>
<Image source={{ uri: image.uri }} style={styles.image} />
<TouchableOpacity
style={[styles.uploadButton, loading && styles.buttonDisabled]}
onPress={uploadImage}
disabled={loading}
>
<Text style={styles.uploadButtonText}>
{loading ? '上传中...' : '上传图片'}
</Text>
</TouchableOpacity>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
alignItems: 'center',
},
pickButton: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 5,
marginBottom: 20,
},
pickButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
imageContainer: {
alignItems: 'center',
},
image: {
width: 200,
height: 200,
borderRadius: 10,
marginBottom: 20,
},
uploadButton: {
backgroundColor: '#4CAF50',
padding: 15,
borderRadius: 5,
},
uploadButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
});使用 Axios
jsx
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Image } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import axios from 'axios';
export default function FileUploadAxiosExample() {
const [image, setImage] = useState(null);
const [loading, setLoading] = useState(false);
const pickImage = async () => {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
Alert.alert('权限错误', '需要相册权限才能选择图片');
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
setImage(result.assets[0]);
}
};
const uploadImage = async () => {
if (!image) {
Alert.alert('错误', '请先选择图片');
return;
}
setLoading(true);
try {
const formData = new FormData();
formData.append('image', {
uri: image.uri,
type: 'image/jpeg',
name: 'photo.jpg',
});
const response = await axios.post('https://api.example.com/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
Alert.alert('成功', '图片上传成功!');
} catch (error) {
Alert.alert('错误', error.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.pickButton} onPress={pickImage}>
<Text style={styles.pickButtonText}>选择图片</Text>
</TouchableOpacity>
{image && (
<View style={styles.imageContainer}>
<Image source={{ uri: image.uri }} style={styles.image} />
<TouchableOpacity
style={[styles.uploadButton, loading && styles.buttonDisabled]}
onPress={uploadImage}
disabled={loading}
>
<Text style={styles.uploadButtonText}>
{loading ? '上传中...' : '上传图片'}
</Text>
</TouchableOpacity>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
alignItems: 'center',
},
pickButton: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 5,
marginBottom: 20,
},
pickButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
imageContainer: {
alignItems: 'center',
},
image: {
width: 200,
height: 200,
borderRadius: 10,
marginBottom: 20,
},
uploadButton: {
backgroundColor: '#4CAF50',
padding: 15,
borderRadius: 5,
},
uploadButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
});4. PUT 和 DELETE 请求
PUT 请求
jsx
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import axios from 'axios';
export default function PutRequestExample() {
const [id, setId] = useState('1');
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [loading, setLoading] = useState(false);
const handleUpdate = async () => {
if (!id || !title || !body) {
Alert.alert('错误', '请填写所有字段');
return;
}
setLoading(true);
try {
const response = await axios.put(`https://jsonplaceholder.typicode.com/posts/${id}`, {
title,
body,
userId: 1,
});
Alert.alert('成功', '更新成功!');
} catch (error) {
Alert.alert('错误', error.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
value={id}
onChangeText={setId}
placeholder="ID"
keyboardType="numeric"
marginBottom={10}
/>
<TextInput
style={styles.input}
value={title}
onChangeText={setTitle}
placeholder="标题"
marginBottom={10}
/>
<TextInput
style={[styles.input, styles.textArea]}
value={body}
onChangeText={setBody}
placeholder="内容"
multiline
numberOfLines={4}
marginBottom={20}
/>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleUpdate}
disabled={loading}
>
<Text style={styles.buttonText}>{loading ? '更新中...' : '更新'}</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
},
textArea: {
height: 100,
textAlignVertical: 'top',
},
button: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 5,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
});DELETE 请求
jsx
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import axios from 'axios';
export default function DeleteRequestExample() {
const [id, setId] = useState('1');
const [loading, setLoading] = useState(false);
const handleDelete = async () => {
if (!id) {
Alert.alert('错误', '请填写ID');
return;
}
Alert.alert(
'确认删除',
`确定要删除 ID 为 ${id} 的记录吗?`,
[
{
text: '取消',
style: 'cancel',
},
{
text: '确定',
onPress: async () => {
setLoading(true);
try {
await axios.delete(`https://jsonplaceholder.typicode.com/posts/${id}`);
Alert.alert('成功', '删除成功!');
} catch (error) {
Alert.alert('错误', error.message);
} finally {
setLoading(false);
}
},
},
]
);
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
value={id}
onChangeText={setId}
placeholder="ID"
keyboardType="numeric"
marginBottom={20}
/>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleDelete}
disabled={loading}
>
<Text style={styles.buttonText}>{loading ? '删除中...' : '删除'}</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
},
button: {
backgroundColor: '#f44336',
padding: 15,
borderRadius: 5,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
});5. 批量请求
并行请求
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import axios from 'axios';
export default function ParallelRequestsExample() {
const [data, setData] = useState({ posts: [], comments: [], users: [] });
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const [postsResponse, commentsResponse, usersResponse] = await Promise.all([
axios.get('https://jsonplaceholder.typicode.com/posts'),
axios.get('https://jsonplaceholder.typicode.com/comments'),
axios.get('https://jsonplaceholder.typicode.com/users'),
]);
setData({
posts: postsResponse.data,
comments: commentsResponse.data,
users: usersResponse.data,
});
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#4CAF50" />
</View>
);
}
if (error) {
return (
<View style={styles.container}>
<Text style={styles.errorText}>错误: {error}</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>并行请求结果</Text>
<Text style={styles.info}>帖子数量: {data.posts.length}</Text>
<Text style={styles.info}>评论数量: {data.comments.length}</Text>
<Text style={styles.info}>用户数量: {data.users.length}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 20,
},
info: {
fontSize: 16,
marginBottom: 10,
},
errorText: {
fontSize: 16,
color: 'red',
},
});串行请求
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import axios from 'axios';
export default function SequentialRequestsExample() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
// 第一步:获取帖子
const postsResponse = await axios.get('https://jsonplaceholder.typicode.com/posts');
const firstPost = postsResponse.data[0];
// 第二步:获取该帖子的评论
const commentsResponse = await axios.get(`https://jsonplaceholder.typicode.com/posts/${firstPost.id}/comments`);
// 第三步:获取作者信息
const userResponse = await axios.get(`https://jsonplaceholder.typicode.com/users/${firstPost.userId}`);
setData({
post: firstPost,
comments: commentsResponse.data,
user: userResponse.data,
});
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#4CAF50" />
</View>
);
}
if (error) {
return (
<View style={styles.container}>
<Text style={styles.errorText}>错误: {error}</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>串行请求结果</Text>
<Text style={styles.postTitle}>{data.post.title}</Text>
<Text style={styles.userName}>作者: {data.user.name}</Text>
<Text style={styles.commentsTitle}>评论数量: {data.comments.length}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 20,
},
postTitle: {
fontSize: 18,
fontWeight: '500',
marginBottom: 10,
},
userName: {
fontSize: 16,
color: '#666',
marginBottom: 10,
},
commentsTitle: {
fontSize: 16,
marginBottom: 10,
},
errorText: {
fontSize: 16,
color: 'red',
},
});6. 常见问题与解决方案
问题 1:跨域请求
问题:遇到跨域请求错误。
解决方案:
- 在开发环境中,可以使用代理服务器
- 在生产环境中,确保服务器设置了正确的 CORS 头
- 使用第三方 API 时,检查是否需要 API 密钥
问题 2:文件上传失败
问题:文件上传失败或进度无法跟踪。
解决方案:
- 确保文件路径正确
- 检查服务器端的文件接收处理
- 使用 FormData 正确构建表单数据
- 对于大文件,可以实现分片上传
问题 3:请求超时
问题:网络请求超时。
解决方案:
- 设置合理的超时时间
- 实现重试机制
- 检查网络连接状态
- 对于大文件上传,考虑使用后台上传
问题 4:认证失败
问题:API 认证失败。
解决方案:
- 确保正确设置了认证头
- 检查 token 是否过期
- 实现 token 刷新机制
- 处理认证错误的边界情况
7. 最佳实践
1. 封装 API 服务
将 API 请求封装到单独的服务文件中:
jsx
// services/api.js
import axios from 'axios';
const API_BASE_URL = 'https://api.example.com';
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器
api.interceptors.request.use(
(config) => {
// 添加认证 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
api.interceptors.response.use(
(response) => {
return response;
},
(error) => {
// 处理错误
if (error.response?.status === 401) {
// 处理认证错误
}
return Promise.reject(error);
}
);
// API 方法
export const apiService = {
// GET 请求
get: (url, params) => api.get(url, { params }),
// POST 请求
post: (url, data) => api.post(url, data),
// PUT 请求
put: (url, data) => api.put(url, data),
// DELETE 请求
delete: (url) => api.delete(url),
// 上传文件
upload: (url, formData) => api.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}),
};2. 使用 TypeScript
为 API 请求添加类型定义:
typescript
// types/api.ts
export interface Post {
id: number;
title: string;
body: string;
userId: number;
}
export interface User {
id: number;
name: string;
email: string;
username: string;
}
export interface Comment {
id: number;
postId: number;
name: string;
email: string;
body: string;
}3. 错误处理
实现统一的错误处理机制:
jsx
// utils/errorHandler.js
export const handleApiError = (error) => {
if (error.response) {
// 服务器返回错误状态码
switch (error.response.status) {
case 400:
return '请求参数错误';
case 401:
return '未授权,请重新登录';
case 403:
return '禁止访问';
case 404:
return '请求的资源不存在';
case 500:
return '服务器内部错误';
default:
return `请求失败: ${error.response.status}`;
}
} else if (error.request) {
// 请求已发出但没有收到响应
return '网络连接失败,请检查网络';
} else {
// 请求配置出错
return error.message;
}
};4. 缓存策略
实现合理的缓存策略,减少重复请求:
jsx
// services/cacheService.js
const cache = new Map();
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟
export const getCachedData = (key) => {
const cachedItem = cache.get(key);
if (cachedItem) {
const { data, timestamp } = cachedItem;
if (Date.now() - timestamp < CACHE_DURATION) {
return data;
}
// 缓存过期,删除
cache.delete(key);
}
return null;
};
export const setCachedData = (key, data) => {
cache.set(key, {
data,
timestamp: Date.now(),
});
};
export const clearCache = (key) => {
if (key) {
cache.delete(key);
} else {
cache.clear();
}
};8. 总结
本文介绍了 React Native 中各种类型的网络请求,包括 GET、POST、PUT、DELETE 和文件上传。通过本文的学习,你应该掌握了以下内容:
- 使用 Fetch API 和 Axios 进行各种类型的网络请求
- 文件上传的实现方法
- 并行和串行请求的处理
- 常见问题的解决方案
- 最佳实践
在实际开发中,合理使用这些技术,可以创建出更加可靠、高效的网络请求功能,提升应用的用户体验。
