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