Appearance
加载状态、错误处理、下拉刷新
第六部分:数据请求与状态管理
在 React Native 应用中,网络请求是常见的功能,而良好的加载状态、错误处理和下拉刷新机制可以显著提升用户体验。本文将详细介绍如何实现这些功能。
1. 加载状态管理
基础加载状态
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator } from 'react-native';
import axios from 'axios';
export default function LoadingStateExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setData(response.data);
setError(null);
} catch (error) {
setError('网络请求失败,请稍后重试');
setData([]);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
<View style={styles.centerContainer}>
<ActivityIndicator size="large" color="#4CAF50" />
<Text style={styles.loadingText}>加载中...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.centerContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
return (
<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>
)}
/>
);
}
const styles = StyleSheet.create({
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
marginTop: 10,
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
textAlign: 'center',
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
});骨架屏 (Skeleton) 加载
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator } from 'react-native';
import axios from 'axios';
export default function SkeletonLoadingExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setData(response.data);
setError(null);
} catch (error) {
setError('网络请求失败,请稍后重试');
setData([]);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const renderSkeleton = () => (
<View style={styles.skeletonContainer}>
{[1, 2, 3, 4, 5].map((item) => (
<View key={item} style={styles.skeletonItem}>
<View style={styles.skeletonTitle} />
<View style={styles.skeletonBody} />
<View style={[styles.skeletonBody, { width: '70%' }]} />
</View>
))}
</View>
);
if (loading) {
return renderSkeleton();
}
if (error) {
return (
<View style={styles.centerContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
return (
<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>
)}
/>
);
}
const styles = StyleSheet.create({
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorText: {
fontSize: 16,
color: 'red',
textAlign: 'center',
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
skeletonContainer: {
flex: 1,
},
skeletonItem: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
skeletonTitle: {
height: 16,
backgroundColor: '#e0e0e0',
borderRadius: 4,
marginBottom: 10,
},
skeletonBody: {
height: 14,
backgroundColor: '#e0e0e0',
borderRadius: 4,
marginBottom: 8,
},
});2. 错误处理
基本错误处理
jsx
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import axios from 'axios';
export default function ErrorHandlingExample() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleLogin = async () => {
if (!email || !password) {
setError('请输入邮箱和密码');
return;
}
setLoading(true);
setError('');
try {
const response = await axios.post('https://api.example.com/login', {
email,
password,
});
// 登录成功
Alert.alert('成功', '登录成功!');
} catch (error) {
if (error.response) {
// 服务器返回错误
switch (error.response.status) {
case 400:
setError('请求参数错误');
break;
case 401:
setError('邮箱或密码错误');
break;
case 500:
setError('服务器内部错误');
break;
default:
setError(`登录失败: ${error.response.status}`);
}
} else if (error.request) {
// 请求已发出但没有收到响应
setError('网络连接失败,请检查网络');
} else {
// 请求配置出错
setError(error.message);
}
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
value={email}
onChangeText={(text) => {
setEmail(text);
setError('');
}}
placeholder="邮箱"
keyboardType="email-address"
autoCapitalize="none"
marginBottom={10}
/>
<TextInput
style={styles.input}
value={password}
onChangeText={(text) => {
setPassword(text);
setError('');
}}
placeholder="密码"
secureTextEntry
marginBottom={20}
/>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleLogin}
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: '#4CAF50',
padding: 15,
borderRadius: 5,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
errorText: {
fontSize: 14,
color: 'red',
marginBottom: 15,
},
});全局错误处理
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;
}
};
// 使用示例
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import axios from 'axios';
import { handleApiError } from '../utils/errorHandler';
export default function GlobalErrorHandlingExample() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleLogin = async () => {
if (!email || !password) {
setError('请输入邮箱和密码');
return;
}
setLoading(true);
setError('');
try {
const response = await axios.post('https://api.example.com/login', {
email,
password,
});
// 登录成功
Alert.alert('成功', '登录成功!');
} catch (error) {
setError(handleApiError(error));
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
value={email}
onChangeText={(text) => {
setEmail(text);
setError('');
}}
placeholder="邮箱"
keyboardType="email-address"
autoCapitalize="none"
marginBottom={10}
/>
<TextInput
style={styles.input}
value={password}
onChangeText={(text) => {
setPassword(text);
setError('');
}}
placeholder="密码"
secureTextEntry
marginBottom={20}
/>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleLogin}
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: '#4CAF50',
padding: 15,
borderRadius: 5,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#9e9e9e',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
errorText: {
fontSize: 14,
color: 'red',
marginBottom: 15,
},
});3. 下拉刷新
基本下拉刷新
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, RefreshControl } from 'react-native';
import axios from 'axios';
export default function PullToRefreshExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setData(response.data);
setError(null);
} catch (error) {
setError('网络请求失败,请稍后重试');
setData([]);
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchData();
}, []);
const handleRefresh = () => {
setRefreshing(true);
fetchData();
};
if (loading) {
return (
<View style={styles.centerContainer}>
<Text style={styles.loadingText}>加载中...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.centerContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
return (
<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>
)}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={['#4CAF50']} // Android
tintColor="#4CAF50" // iOS
/>
}
/>
);
}
const styles = StyleSheet.create({
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
textAlign: 'center',
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
});下拉刷新与上拉加载更多
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, RefreshControl, ActivityIndicator } from 'react-native';
import axios from 'axios';
export default function PullToRefreshWithLoadMoreExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [error, setError] = useState(null);
const fetchData = async (isRefresh = false) => {
try {
const currentPage = isRefresh ? 1 : page;
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts?_page=${currentPage}&_limit=10`);
if (isRefresh) {
setData(response.data);
} else {
setData(prevData => [...prevData, ...response.data]);
}
setHasMore(response.data.length === 10);
setPage(currentPage + 1);
setError(null);
} catch (error) {
setError('网络请求失败,请稍后重试');
} finally {
setLoading(false);
setRefreshing(false);
setLoadingMore(false);
}
};
useEffect(() => {
fetchData();
}, []);
const handleRefresh = () => {
setRefreshing(true);
setPage(1);
fetchData(true);
};
const handleLoadMore = () => {
if (!loadingMore && hasMore) {
setLoadingMore(true);
fetchData();
}
};
const renderFooter = () => {
if (!loadingMore) return null;
return (
<View style={styles.footer}>
<ActivityIndicator size="small" color="#4CAF50" />
<Text style={styles.footerText}>加载更多...</Text>
</View>
);
};
if (loading) {
return (
<View style={styles.centerContainer}>
<Text style={styles.loadingText}>加载中...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.centerContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
return (
<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>
)}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={['#4CAF50']}
tintColor="#4CAF50"
/>
}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.1}
ListFooterComponent={renderFooter}
/>
);
}
const styles = StyleSheet.create({
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
textAlign: 'center',
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
footer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
padding: 15,
},
footerText: {
marginLeft: 10,
fontSize: 14,
color: '#666',
},
});4. 网络状态检测
使用 NetInfo 检测网络状态
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, FlatList, RefreshControl, Alert } from 'react-native';
import NetInfo from '@react-native-community/netinfo';
import axios from 'axios';
export default function NetworkStatusExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [isConnected, setIsConnected] = useState(true);
const [error, setError] = useState(null);
const fetchData = async () => {
if (!isConnected) {
setError('网络连接已断开,请检查网络');
setLoading(false);
setRefreshing(false);
return;
}
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setData(response.data);
setError(null);
} catch (error) {
setError('网络请求失败,请稍后重试');
setData([]);
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
if (state.isConnected) {
fetchData();
}
});
fetchData();
return () => unsubscribe();
}, []);
const handleRefresh = () => {
setRefreshing(true);
fetchData();
};
if (loading) {
return (
<View style={styles.centerContainer}>
<Text style={styles.loadingText}>加载中...</Text>
</View>
);
}
return (
<View style={styles.container}>
{!isConnected && (
<View style={styles.networkStatusBar}>
<Text style={styles.networkStatusText}>网络连接已断开</Text>
</View>
)}
{error ? (
<View style={styles.centerContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
) : (
<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>
)}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={['#4CAF50']}
tintColor="#4CAF50"
/>
}
/>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
textAlign: 'center',
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
networkStatusBar: {
backgroundColor: '#ff9800',
padding: 10,
alignItems: 'center',
},
networkStatusText: {
color: '#fff',
fontSize: 14,
},
});5. 缓存策略
离线缓存
jsx
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, RefreshControl, Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import axios from 'axios';
export default function OfflineCacheExample() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState(null);
const CACHE_KEY = 'posts_cache';
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟
const fetchData = async () => {
try {
// 先尝试从缓存获取数据
const cachedData = await AsyncStorage.getItem(CACHE_KEY);
if (cachedData) {
const { data: cachedPosts, timestamp } = JSON.parse(cachedData);
if (Date.now() - timestamp < CACHE_DURATION) {
setData(cachedPosts);
setError(null);
setLoading(false);
setRefreshing(false);
return;
}
}
// 缓存过期或不存在,从网络获取
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
// 保存到缓存
await AsyncStorage.setItem(CACHE_KEY, JSON.stringify({
data: response.data,
timestamp: Date.now(),
}));
setData(response.data);
setError(null);
} catch (error) {
// 网络错误,尝试使用缓存数据
const cachedData = await AsyncStorage.getItem(CACHE_KEY);
if (cachedData) {
const { data: cachedPosts } = JSON.parse(cachedData);
setData(cachedPosts);
setError('网络连接失败,显示缓存数据');
} else {
setError('网络请求失败,请稍后重试');
setData([]);
}
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchData();
}, []);
const handleRefresh = () => {
setRefreshing(true);
fetchData();
};
if (loading) {
return (
<View style={styles.centerContainer}>
<Text style={styles.loadingText}>加载中...</Text>
</View>
);
}
return (
<View style={styles.container}>
{error ? (
<View style={styles.errorBar}>
<Text style={styles.errorBarText}>{error}</Text>
</View>
) : null}
<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>
)}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={['#4CAF50']}
tintColor="#4CAF50"
/>
}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
textAlign: 'center',
},
errorBar: {
backgroundColor: '#ffebee',
padding: 10,
alignItems: 'center',
},
errorBarText: {
color: '#c62828',
fontSize: 14,
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
});6. 最佳实践
1. 统一封装网络请求
jsx
// services/api.js
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
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(
async (config) => {
// 添加认证 token
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) => {
return response;
},
(error) => {
// 处理错误
if (error.response?.status === 401) {
// 处理认证错误
AsyncStorage.removeItem('token');
// 导航到登录页
}
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. 使用自定义 Hook 管理网络状态
jsx
// hooks/useApi.js
import { useState, useEffect } from 'react';
import { apiService } from '../services/api';
export const useApi = (endpoint, options = {}) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [refreshing, setRefreshing] = useState(false);
const fetchData = async (isRefresh = false) => {
try {
if (isRefresh) setRefreshing(true);
else setLoading(true);
setError(null);
const response = await apiService[options.method || 'get'](
endpoint,
options.params || options.data
);
setData(response.data);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchData();
}, [endpoint, options.method, JSON.stringify(options.params), JSON.stringify(options.data)]);
return { data, loading, error, refreshing, refetch: () => fetchData(true) };
};
// 使用示例
import React from 'react';
import { View, Text, FlatList, StyleSheet, RefreshControl } from 'react-native';
import { useApi } from '../hooks/useApi';
export default function UseApiExample() {
const { data, loading, error, refreshing, refetch } = useApi('/posts');
if (loading) {
return (
<View style={styles.centerContainer}>
<Text style={styles.loadingText}>加载中...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.centerContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
return (
<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>
)}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={refetch}
colors={['#4CAF50']}
tintColor="#4CAF50"
/>
}
/>
);
}
const styles = StyleSheet.create({
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: 'red',
textAlign: 'center',
},
item: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
body: {
fontSize: 14,
color: '#666',
},
});3. 错误边界
jsx
// components/ErrorBoundary.js
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
};
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<View style={styles.container}>
<Text style={styles.errorText}>发生错误</Text>
<Text style={styles.errorMessage}>{this.state.error?.message || '未知错误'}</Text>
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ hasError: false, error: null })}
>
<Text style={styles.buttonText}>重试</Text>
</TouchableOpacity>
</View>
);
}
return this.props.children;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorText: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
color: 'red',
},
errorMessage: {
fontSize: 14,
color: '#666',
textAlign: 'center',
marginBottom: 20,
},
button: {
backgroundColor: '#4CAF50',
padding: 10,
borderRadius: 5,
},
buttonText: {
color: '#fff',
fontSize: 16,
},
});
// 使用示例
import React from 'react';
import ErrorBoundary from '../components/ErrorBoundary';
import NetworkStatusExample from './NetworkStatusExample';
export default function App() {
return (
<ErrorBoundary>
<NetworkStatusExample />
</ErrorBoundary>
);
}7. 总结
本文介绍了 React Native 中网络请求的加载状态管理、错误处理和下拉刷新机制。通过本文的学习,你应该掌握了以下内容:
- 如何实现基本的加载状态和骨架屏加载
- 如何处理各种类型的网络错误
- 如何实现下拉刷新和上拉加载更多
- 如何检测网络状态
- 如何实现离线缓存
- 最佳实践和代码封装
在实际开发中,合理使用这些技术,可以创建出更加可靠、用户友好的网络请求功能,提升应用的整体用户体验。
