React Native跨平台开发详解
React Native是Facebook开发的开源框架,允许开发者使用React和JavaScript构建原生移动应用程序。它结合了原生开发的最佳部分与React的优势,让开发者能够使用一套代码库同时为iOS和Android平台构建应用。
核心价值
React Native = React语法 + 原生性能 + 跨平台开发 + 热重载
- ⚛️ React生态:使用熟悉的React语法和生态系统
- 📱 原生性能:编译为真正的原生组件,性能接近原生应用
- 🔄 代码复用:一套代码同时运行在iOS和Android平台
- 🔥 快速开发:热重载功能,实时查看代码更改效果
- 🌉 原生桥接:轻松集成原生模块和第三方库
- 👥 活跃社区:庞大的开发者社区和丰富的第三方组件
1. 环境搭建与项目初始化
1.1 开发环境配置
React Native开发需要配置相应的开发环境,包括Node.js、React Native CLI、Android Studio和Xcode等。
- 环境搭建
- 配置优化
- 调试工具
开发环境搭建指南
环境搭建步骤
bash
1# 1. 安装Node.js (推荐使用LTS版本)2# 从 https://nodejs.org 下载并安装34# 2. 安装React Native CLI5npm install -g @react-native-community/cli67# 3. 创建新项目8npx react-native init MyAwesomeApp910# 或使用TypeScript模板11npx react-native init MyAwesomeApp --template react-native-template-typescript1213# 4. 进入项目目录14cd MyAwesomeApp1516# 5. 安装依赖17npm install18# 或19yarn install2021# 6. iOS依赖安装 (仅macOS)22cd ios && pod install && cd ..2324# 7. 启动Metro服务器25npx react-native start2627# 8. 运行应用28# Android29npx react-native run-android3031# iOS (仅macOS)32npx react-native run-ios项目结构解析
1MyAwesomeApp/2├── android/ # Android原生代码3│ ├── app/4│ │ ├── src/main/5│ │ │ ├── java/6│ │ │ └── res/7│ │ └── build.gradle8│ └── build.gradle9├── ios/ # iOS原生代码10│ ├── MyAwesomeApp/11│ │ ├── AppDelegate.h12│ │ ├── AppDelegate.m13│ │ └── Info.plist14│ └── MyAwesomeApp.xcodeproj/15├── src/ # 源代码目录16│ ├── components/ # 组件17│ ├── screens/ # 页面18│ ├── navigation/ # 导航配置19│ ├── services/ # 服务层20│ ├── utils/ # 工具函数21│ └── types/ # TypeScript类型定义22├── __tests__/ # 测试文件23├── App.tsx # 应用入口24├── index.js # 注册入口25├── package.json # 项目配置26├── metro.config.js # Metro配置27├── babel.config.js # Babel配置28└── tsconfig.json # TypeScript配置开发配置优化
metro.config.js - Metro配置优化
javascript
1const { getDefaultConfig } = require('metro-config');23module.exports = (async () => {4 const {5 resolver: { sourceExts, assetExts },6 } = await getDefaultConfig();7 8 return {9 transformer: {10 babelTransformerPath: require.resolve('react-native-svg-transformer'),11 getTransformOptions: async () => ({12 transform: {13 experimentalImportSupport: false,14 inlineRequires: true,15 },16 }),17 },18 resolver: {19 assetExts: assetExts.filter(ext => ext !== 'svg'),20 sourceExts: [...sourceExts, 'svg'],21 alias: {22 '@': './src',23 '@components': './src/components',24 '@screens': './src/screens',25 '@services': './src/services',26 '@utils': './src/utils',27 '@types': './src/types',28 },29 },30 watchFolders: [],31 };32})();tsconfig.json - TypeScript配置
json
1{2 "compilerOptions": {3 "target": "esnext",4 "lib": ["es2017"],5 "allowJs": true,6 "skipLibCheck": true,7 "esModuleInterop": true,8 "allowSyntheticDefaultImports": true,9 "strict": true,10 "forceConsistentCasingInFileNames": true,11 "moduleResolution": "node",12 "resolveJsonModule": true,13 "isolatedModules": true,14 "noEmit": true,15 "jsx": "react-jsx",16 "baseUrl": "./",17 "paths": {18 "@/*": ["src/*"],19 "@components/*": ["src/components/*"],20 "@screens/*": ["src/screens/*"],21 "@services/*": ["src/services/*"],22 "@utils/*": ["src/utils/*"],23 "@types/*": ["src/types/*"]24 }25 },26 "include": [27 "src/**/*",28 "App.tsx",29 "index.js"30 ],31 "exclude": [32 "node_modules",33 "babel.config.js",34 "metro.config.js",35 "jest.config.js"36 ]37}babel.config.js - Babel配置
javascript
1module.exports = {2 presets: ['module:metro-react-native-babel-preset'],3 plugins: [4 [5 'module-resolver',6 {7 root: ['./src'],8 extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],9 alias: {10 '@': './src',11 '@components': './src/components',12 '@screens': './src/screens',13 '@services': './src/services',14 '@utils': './src/utils',15 '@types': './src/types',16 },17 },18 ],19 'react-native-reanimated/plugin', // 必须放在最后20 ],21};调试工具配置
调试工具集成
javascript
1// Flipper调试工具配置2// 在App.tsx中添加3import { useEffect } from 'react';45if (__DEV__) {6 import('./ReactotronConfig').then(() => console.log('Reactotron Configured'));7}89// ReactotronConfig.js10import Reactotron from 'reactotron-react-native';11import { reactotronRedux } from 'reactotron-redux';12import AsyncStorage from '@react-native-async-storage/async-storage';1314const reactotron = Reactotron15 .setAsyncStorageHandler(AsyncStorage)16 .configure({17 name: 'MyAwesomeApp',18 })19 .useReactNative({20 asyncStorage: false,21 networking: {22 ignoreUrls: /symbolicate/,23 },24 editor: false,25 errors: { veto: (stackFrame) => false },26 overlay: false,27 })28 .use(reactotronRedux())29 .connect();3031export default reactotron;3233// 性能监控34class PerformanceMonitor {35 static startTiming(label) {36 console.time(label);37 }38 39 static endTiming(label) {40 console.timeEnd(label);41 }42 43 static measureRender(Component) {44 return function MeasuredComponent(props) {45 useEffect(() => {46 const startTime = performance.now();47 return () => {48 const endTime = performance.now();49 console.log(`${Component.name} render time: ${endTime - startTime}ms`);50 };51 });52 53 return <Component {...props} />;54 };55 }56}5758// 网络请求监控59const originalFetch = global.fetch;60global.fetch = function(...args) {61 const startTime = Date.now();62 const url = args[0];63 64 console.log(`🌐 Request: ${url}`);65 66 return originalFetch.apply(this, args)67 .then(response => {68 const duration = Date.now() - startTime;69 console.log(`✅ Response: ${url} (${duration}ms)`);70 return response;71 })72 .catch(error => {73 const duration = Date.now() - startTime;74 console.log(`❌ Error: ${url} (${duration}ms)`, error);75 throw error;76 });77};2. 核心组件与API
2.1 基础组件
React Native提供了丰富的内置组件,这些组件会被编译为对应平台的原生组件。
- 基础组件
- 交互组件
- 列表组件
基础组件使用
基础组件示例
tsx
1import React, { useState } from 'react';2import {3 View,4 Text,5 Image,6 ScrollView,7 StyleSheet,8 Dimensions,9 SafeAreaView,10} from 'react-native';1112const { width, height } = Dimensions.get('window');1314const BasicComponentsExample: React.FC = () => {15 return (16 <SafeAreaView style={styles.container}>17 <ScrollView 18 style={styles.scrollView}19 showsVerticalScrollIndicator={false}20 contentContainerStyle={styles.scrollContent}21 >22 {/* View组件 - 容器 */}23 <View style={styles.section}>24 <Text style={styles.sectionTitle}>View组件</Text>25 <View style={styles.colorBox}>26 <Text style={styles.boxText}>这是一个View容器</Text>27 </View>28 </View>2930 {/* Text组件 - 文本显示 */}31 <View style={styles.section}>32 <Text style={styles.sectionTitle}>Text组件</Text>33 <Text style={styles.normalText}>普通文本</Text>34 <Text style={styles.boldText}>粗体文本</Text>35 <Text style={styles.colorText}>彩色文本</Text>36 <Text 37 style={styles.longText}38 numberOfLines={2}39 ellipsizeMode="tail"40 >41 这是一段很长的文本,用来演示numberOfLines和ellipsizeMode属性的使用效果42 </Text>43 </View>4445 {/* Image组件 - 图片显示 */}46 <View style={styles.section}>47 <Text style={styles.sectionTitle}>Image组件</Text>48 49 {/* 本地图片 */}50 <Image51 source={require('./assets/logo.png')}52 style={styles.localImage}53 resizeMode="contain"54 />55 56 {/* 网络图片 */}57 <Image58 source={{59 uri: 'https://picsum.photos/200/150',60 cache: 'force-cache',61 }}62 style={styles.networkImage}63 resizeMode="cover"64 onLoad={() => console.log('图片加载完成')}65 onError={(error) => console.log('图片加载失败', error)}66 />67 68 {/* 带加载状态的图片 */}69 <ImageWithLoading70 source={{ uri: 'https://picsum.photos/300/200' }}71 style={styles.networkImage}72 />73 </View>7475 {/* ScrollView组件 - 滚动容器 */}76 <View style={styles.section}>77 <Text style={styles.sectionTitle}>ScrollView组件</Text>78 79 {/* 水平滚动 */}80 <ScrollView 81 horizontal82 showsHorizontalScrollIndicator={false}83 style={styles.horizontalScroll}84 >85 {[1, 2, 3, 4, 5].map(item => (86 <View key={item} style={styles.horizontalItem}>87 <Text style={styles.itemText}>{item}</Text>88 </View>89 ))}90 </ScrollView>91 </View>92 </ScrollView>93 </SafeAreaView>94 );95};9697// 带加载状态的图片组件98const ImageWithLoading: React.FC<{99 source: { uri: string };100 style: any;101}> = ({ source, style }) => {102 const [loading, setLoading] = useState(true);103 const [error, setError] = useState(false);104105 return (106 <View style={[style, styles.imageContainer]}>107 {loading && !error && (108 <View style={styles.loadingContainer}>109 <Text>加载中...</Text>110 </View>111 )}112 {error && (113 <View style={styles.errorContainer}>114 <Text>加载失败</Text>115 </View>116 )}117 <Image118 source={source}119 style={style}120 onLoad={() => setLoading(false)}121 onError={() => {122 setLoading(false);123 setError(true);124 }}125 />126 </View>127 );128};129130const styles = StyleSheet.create({131 container: {132 flex: 1,133 backgroundColor: '#f5f5f5',134 },135 scrollView: {136 flex: 1,137 },138 scrollContent: {139 padding: 16,140 },141 section: {142 marginBottom: 24,143 backgroundColor: 'white',144 borderRadius: 8,145 padding: 16,146 shadowColor: '#000',147 shadowOffset: {148 width: 0,149 height: 2,150 },151 shadowOpacity: 0.1,152 shadowRadius: 3.84,153 elevation: 5,154 },155 sectionTitle: {156 fontSize: 18,157 fontWeight: 'bold',158 marginBottom: 12,159 color: '#333',160 },161 colorBox: {162 backgroundColor: '#4CAF50',163 padding: 16,164 borderRadius: 8,165 alignItems: 'center',166 },167 boxText: {168 color: 'white',169 fontSize: 16,170 fontWeight: '500',171 },172 normalText: {173 fontSize: 16,174 marginBottom: 8,175 color: '#333',176 },177 boldText: {178 fontSize: 16,179 fontWeight: 'bold',180 marginBottom: 8,181 color: '#333',182 },183 colorText: {184 fontSize: 16,185 color: '#2196F3',186 marginBottom: 8,187 },188 longText: {189 fontSize: 14,190 color: '#666',191 lineHeight: 20,192 },193 localImage: {194 width: 100,195 height: 100,196 marginBottom: 12,197 },198 networkImage: {199 width: width - 64,200 height: 150,201 borderRadius: 8,202 marginBottom: 12,203 },204 imageContainer: {205 position: 'relative',206 },207 loadingContainer: {208 position: 'absolute',209 top: 0,210 left: 0,211 right: 0,212 bottom: 0,213 justifyContent: 'center',214 alignItems: 'center',215 backgroundColor: '#f0f0f0',216 zIndex: 1,217 },218 errorContainer: {219 position: 'absolute',220 top: 0,221 left: 0,222 right: 0,223 bottom: 0,224 justifyContent: 'center',225 alignItems: 'center',226 backgroundColor: '#ffebee',227 zIndex: 1,228 },229 horizontalScroll: {230 height: 80,231 },232 horizontalItem: {233 width: 60,234 height: 60,235 backgroundColor: '#FF9800',236 marginRight: 12,237 borderRadius: 30,238 justifyContent: 'center',239 alignItems: 'center',240 },241 itemText: {242 color: 'white',243 fontSize: 18,244 fontWeight: 'bold',245 },246});247248export default BasicComponentsExample;交互组件使用
交互组件示例
tsx
1import React, { useState } from 'react';2import {3 View,4 Text,5 TouchableOpacity,6 TouchableHighlight,7 TouchableWithoutFeedback,8 Pressable,9 Button,10 TextInput,11 Switch,12 Slider,13 Alert,14 StyleSheet,15 SafeAreaView,16 ScrollView,17} from 'react-native';1819const InteractiveComponentsExample: React.FC = () => {20 const [text, setText] = useState('');21 const [isEnabled, setIsEnabled] = useState(false);22 const [sliderValue, setSliderValue] = useState(50);23 const [pressCount, setPressCount] = useState(0);2425 const showAlert = () => {26 Alert.alert(27 '提示',28 '这是一个警告对话框',29 [30 { text: '取消', style: 'cancel' },31 { text: '确定', onPress: () => console.log('确定被点击') },32 ]33 );34 };3536 const showActionSheet = () => {37 Alert.alert(38 '选择操作',39 '请选择要执行的操作',40 [41 { text: '拍照', onPress: () => console.log('拍照') },42 { text: '从相册选择', onPress: () => console.log('相册') },43 { text: '取消', style: 'cancel' },44 ]45 );46 };4748 return (49 <SafeAreaView style={styles.container}>50 <ScrollView style={styles.scrollView}>51 {/* TouchableOpacity - 透明度变化 */}52 <View style={styles.section}>53 <Text style={styles.sectionTitle}>TouchableOpacity</Text>54 <TouchableOpacity55 style={styles.button}56 activeOpacity={0.7}57 onPress={() => setPressCount(pressCount + 1)}58 >59 <Text style={styles.buttonText}>60 点击我 (已点击 {pressCount} 次)61 </Text>62 </TouchableOpacity>63 </View>6465 {/* TouchableHighlight - 高亮效果 */}66 <View style={styles.section}>67 <Text style={styles.sectionTitle}>TouchableHighlight</Text>68 <TouchableHighlight69 style={styles.button}70 underlayColor="#DDDDDD"71 onPress={showAlert}72 >73 <Text style={styles.buttonText}>显示警告对话框</Text>74 </TouchableHighlight>75 </View>7677 {/* Pressable - 现代触摸组件 */}78 <View style={styles.section}>79 <Text style={styles.sectionTitle}>Pressable</Text>80 <Pressable81 style={({ pressed }) => [82 styles.button,83 pressed && styles.buttonPressed84 ]}85 onPress={showActionSheet}86 onLongPress={() => Alert.alert('长按', '长按事件触发')}87 >88 {({ pressed }) => (89 <Text style={styles.buttonText}>90 {pressed ? '按下中...' : '长按或点击我'}91 </Text>92 )}93 </Pressable>94 </View>9596 {/* Button - 系统按钮 */}97 <View style={styles.section}>98 <Text style={styles.sectionTitle}>Button</Text>99 <View style={styles.buttonRow}>100 <Button101 title="主要按钮"102 onPress={() => console.log('主要按钮')}103 />104 <Button105 title="次要按钮"106 onPress={() => console.log('次要按钮')}107 color="#FF6B6B"108 />109 </View>110 </View>111112 {/* TextInput - 文本输入 */}113 <View style={styles.section}>114 <Text style={styles.sectionTitle}>TextInput</Text>115 116 {/* 基础输入框 */}117 <TextInput118 style={styles.textInput}119 placeholder="请输入文本"120 value={text}121 onChangeText={setText}122 clearButtonMode="while-editing"123 />124 125 {/* 多行输入框 */}126 <TextInput127 style={[styles.textInput, styles.multilineInput]}128 placeholder="多行文本输入"129 multiline130 numberOfLines={4}131 textAlignVertical="top"132 />133 134 {/* 密码输入框 */}135 <TextInput136 style={styles.textInput}137 placeholder="请输入密码"138 secureTextEntry139 autoCapitalize="none"140 />141 142 {/* 数字输入框 */}143 <TextInput144 style={styles.textInput}145 placeholder="请输入数字"146 keyboardType="numeric"147 returnKeyType="done"148 />149 150 <Text style={styles.inputDisplay}>151 当前输入: {text}152 </Text>153 </View>154155 {/* Switch - 开关 */}156 <View style={styles.section}>157 <Text style={styles.sectionTitle}>Switch</Text>158 <View style={styles.switchRow}>159 <Text style={styles.switchLabel}>启用通知</Text>160 <Switch161 trackColor={{ false: '#767577', true: '#81b0ff' }}162 thumbColor={isEnabled ? '#f5dd4b' : '#f4f3f4'}163 ios_backgroundColor="#3e3e3e"164 onValueChange={setIsEnabled}165 value={isEnabled}166 />167 </View>168 <Text style={styles.switchStatus}>169 状态: {isEnabled ? '开启' : '关闭'}170 </Text>171 </View>172173 {/* Slider - 滑块 */}174 <View style={styles.section}>175 <Text style={styles.sectionTitle}>Slider</Text>176 <Slider177 style={styles.slider}178 minimumValue={0}179 maximumValue={100}180 value={sliderValue}181 onValueChange={setSliderValue}182 minimumTrackTintColor="#1976D2"183 maximumTrackTintColor="#d3d3d3"184 thumbTintColor="#1976D2"185 />186 <Text style={styles.sliderValue}>187 当前值: {Math.round(sliderValue)}188 </Text>189 </View>190191 {/* 自定义交互组件 */}192 <View style={styles.section}>193 <Text style={styles.sectionTitle}>自定义交互组件</Text>194 <CustomButton195 title="自定义按钮"196 onPress={() => Alert.alert('自定义', '自定义按钮被点击')}197 variant="primary"198 />199 <CustomButton200 title="次要按钮"201 onPress={() => Alert.alert('自定义', '次要按钮被点击')}202 variant="secondary"203 />204 </View>205 </ScrollView>206 </SafeAreaView>207 );208};209210// 自定义按钮组件211interface CustomButtonProps {212 title: string;213 onPress: () => void;214 variant?: 'primary' | 'secondary';215 disabled?: boolean;216}217218const CustomButton: React.FC<CustomButtonProps> = ({219 title,220 onPress,221 variant = 'primary',222 disabled = false,223}) => {224 return (225 <Pressable226 style={({ pressed }) => [227 styles.customButton,228 variant === 'primary' ? styles.primaryButton : styles.secondaryButton,229 pressed && styles.customButtonPressed,230 disabled && styles.customButtonDisabled,231 ]}232 onPress={onPress}233 disabled={disabled}234 >235 <Text236 style={[237 styles.customButtonText,238 variant === 'secondary' && styles.secondaryButtonText,239 disabled && styles.customButtonTextDisabled,240 ]}241 >242 {title}243 </Text>244 </Pressable>245 );246};247248const styles = StyleSheet.create({249 container: {250 flex: 1,251 backgroundColor: '#f5f5f5',252 },253 scrollView: {254 flex: 1,255 padding: 16,256 },257 section: {258 backgroundColor: 'white',259 borderRadius: 8,260 padding: 16,261 marginBottom: 16,262 shadowColor: '#000',263 shadowOffset: {264 width: 0,265 height: 2,266 },267 shadowOpacity: 0.1,268 shadowRadius: 3.84,269 elevation: 5,270 },271 sectionTitle: {272 fontSize: 18,273 fontWeight: 'bold',274 marginBottom: 12,275 color: '#333',276 },277 button: {278 backgroundColor: '#2196F3',279 padding: 12,280 borderRadius: 8,281 alignItems: 'center',282 marginBottom: 8,283 },284 buttonPressed: {285 backgroundColor: '#1976D2',286 },287 buttonText: {288 color: 'white',289 fontSize: 16,290 fontWeight: '500',291 },292 buttonRow: {293 flexDirection: 'row',294 justifyContent: 'space-around',295 },296 textInput: {297 borderWidth: 1,298 borderColor: '#ddd',299 borderRadius: 8,300 padding: 12,301 fontSize: 16,302 marginBottom: 12,303 backgroundColor: '#fafafa',304 },305 multilineInput: {306 height: 100,307 },308 inputDisplay: {309 fontSize: 14,310 color: '#666',311 fontStyle: 'italic',312 },313 switchRow: {314 flexDirection: 'row',315 justifyContent: 'space-between',316 alignItems: 'center',317 marginBottom: 8,318 },319 switchLabel: {320 fontSize: 16,321 color: '#333',322 },323 switchStatus: {324 fontSize: 14,325 color: '#666',326 },327 slider: {328 width: '100%',329 height: 40,330 marginBottom: 8,331 },332 sliderValue: {333 textAlign: 'center',334 fontSize: 16,335 color: '#333',336 },337 customButton: {338 padding: 12,339 borderRadius: 8,340 alignItems: 'center',341 marginBottom: 8,342 },343 primaryButton: {344 backgroundColor: '#4CAF50',345 },346 secondaryButton: {347 backgroundColor: 'transparent',348 borderWidth: 1,349 borderColor: '#4CAF50',350 },351 customButtonPressed: {352 opacity: 0.8,353 },354 customButtonDisabled: {355 backgroundColor: '#ccc',356 borderColor: '#ccc',357 },358 customButtonText: {359 color: 'white',360 fontSize: 16,361 fontWeight: '500',362 },363 secondaryButtonText: {364 color: '#4CAF50',365 },366 customButtonTextDisabled: {367 color: '#999',368 },369});370371export default InteractiveComponentsExample;列表组件使用
列表组件示例
tsx
1import React, { useState, useCallback, useMemo } from 'react';2import {3 View,4 Text,5 FlatList,6 SectionList,7 TouchableOpacity,8 Image,9 RefreshControl,10 ActivityIndicator,11 StyleSheet,12 SafeAreaView,13 Alert,14} from 'react-native';1516// 数据类型定义17interface User {18 id: string;19 name: string;20 email: string;21 avatar: string;22 age: number;23}2425interface Section {26 title: string;27 data: User[];28}2930const ListComponentsExample: React.FC = () => {31 const [users, setUsers] = useState<User[]>(generateUsers(50));32 const [refreshing, setRefreshing] = useState(false);33 const [loading, setLoading] = useState(false);3435 // 生成模拟数据36 function generateUsers(count: number): User[] {37 return Array.from({ length: count }, (_, index) => ({38 id: `user-${index}`,39 name: `用户 ${index + 1}`,40 email: `user${index + 1}@example.com`,41 avatar: `https://picsum.photos/60/60?random=${index}`,42 age: 20 + Math.floor(Math.random() * 40),43 }));44 }4546 // 下拉刷新47 const onRefresh = useCallback(() => {48 setRefreshing(true);49 setTimeout(() => {50 setUsers(generateUsers(50));51 setRefreshing(false);52 }, 2000);53 }, []);5455 // 加载更多56 const loadMore = useCallback(() => {57 if (loading) return;58 59 setLoading(true);60 setTimeout(() => {61 const newUsers = generateUsers(20);62 const startId = users.length;63 const updatedUsers = newUsers.map((user, index) => ({64 ...user,65 id: `user-${startId + index}`,66 name: `用户 ${startId + index + 1}`,67 }));68 setUsers(prevUsers => [...prevUsers, ...updatedUsers]);69 setLoading(false);70 }, 1500);71 }, [users.length, loading]);7273 // 按年龄分组的数据74 const sectionData = useMemo(() => {75 const grouped = users.reduce((acc, user) => {76 const ageGroup = Math.floor(user.age / 10) * 10;77 const key = `${ageGroup}-${ageGroup + 9}岁`;78 79 if (!acc[key]) {80 acc[key] = [];81 }82 acc[key].push(user);83 return acc;84 }, {} as Record<string, User[]>);8586 return Object.entries(grouped).map(([title, data]) => ({87 title,88 data: data.sort((a, b) => a.age - b.age),89 }));90 }, [users]);9192 // 渲染用户项93 const renderUserItem = useCallback(({ item, index }: { item: User; index: number }) => (94 <UserItem95 user={item}96 index={index}97 onPress={() => Alert.alert('用户信息', `姓名: ${item.name}\n邮箱: ${item.email}`)}98 />99 ), []);100101 // 渲染分组标题102 const renderSectionHeader = useCallback(({ section }: { section: Section }) => (103 <View style={styles.sectionHeader}>104 <Text style={styles.sectionHeaderText}>{section.title}</Text>105 <Text style={styles.sectionCount}>({section.data.length}人)</Text>106 </View>107 ), []);108109 // 渲染加载更多110 const renderFooter = useCallback(() => {111 if (!loading) return null;112 113 return (114 <View style={styles.loadingFooter}>115 <ActivityIndicator size="small" color="#2196F3" />116 <Text style={styles.loadingText}>加载中...</Text>117 </View>118 );119 }, [loading]);120121 // 渲染空状态122 const renderEmpty = useCallback(() => (123 <View style={styles.emptyContainer}>124 <Text style={styles.emptyText}>暂无数据</Text>125 <TouchableOpacity style={styles.retryButton} onPress={onRefresh}>126 <Text style={styles.retryButtonText}>重试</Text>127 </TouchableOpacity>128 </View>129 ), [onRefresh]);130131 return (132 <SafeAreaView style={styles.container}>133 <View style={styles.tabContainer}>134 <TouchableOpacity style={styles.tab}>135 <Text style={styles.tabText}>FlatList</Text>136 </TouchableOpacity>137 <TouchableOpacity style={styles.tab}>138 <Text style={styles.tabText}>SectionList</Text>139 </TouchableOpacity>140 </View>141142 {/* FlatList示例 */}143 <View style={styles.listContainer}>144 <Text style={styles.listTitle}>FlatList - 用户列表</Text>145 <FlatList146 data={users}147 renderItem={renderUserItem}148 keyExtractor={(item) => item.id}149 refreshControl={150 <RefreshControl151 refreshing={refreshing}152 onRefresh={onRefresh}153 colors={['#2196F3']}154 tintColor="#2196F3"155 />156 }157 onEndReached={loadMore}158 onEndReachedThreshold={0.1}159 ListFooterComponent={renderFooter}160 ListEmptyComponent={renderEmpty}161 ItemSeparatorComponent={() => <View style={styles.separator} />}162 showsVerticalScrollIndicator={false}163 removeClippedSubviews={true}164 maxToRenderPerBatch={10}165 windowSize={10}166 initialNumToRender={15}167 getItemLayout={(data, index) => ({168 length: 80,169 offset: 80 * index,170 index,171 })}172 />173 </View>174175 {/* SectionList示例 */}176 <View style={styles.listContainer}>177 <Text style={styles.listTitle}>SectionList - 按年龄分组</Text>178 <SectionList179 sections={sectionData}180 renderItem={renderUserItem}181 renderSectionHeader={renderSectionHeader}182 keyExtractor={(item) => item.id}183 refreshControl={184 <RefreshControl185 refreshing={refreshing}186 onRefresh={onRefresh}187 colors={['#2196F3']}188 tintColor="#2196F3"189 />190 }191 ItemSeparatorComponent={() => <View style={styles.separator} />}192 SectionSeparatorComponent={() => <View style={styles.sectionSeparator} />}193 showsVerticalScrollIndicator={false}194 stickySectionHeadersEnabled={true}195 />196 </View>197 </SafeAreaView>198 );199};200201// 用户项组件202const UserItem: React.FC<{203 user: User;204 index: number;205 onPress: () => void;206}> = React.memo(({ user, index, onPress }) => {207 return (208 <TouchableOpacity style={styles.userItem} onPress={onPress}>209 <Image source={{ uri: user.avatar }} style={styles.avatar} />210 <View style={styles.userInfo}>211 <Text style={styles.userName}>{user.name}</Text>212 <Text style={styles.userEmail}>{user.email}</Text>213 <Text style={styles.userAge}>年龄: {user.age}岁</Text>214 </View>215 <View style={styles.indexBadge}>216 <Text style={styles.indexText}>{index + 1}</Text>217 </View>218 </TouchableOpacity>219 );220});221222const styles = StyleSheet.create({223 container: {224 flex: 1,225 backgroundColor: '#f5f5f5',226 },227 tabContainer: {228 flexDirection: 'row',229 backgroundColor: 'white',230 paddingHorizontal: 16,231 },232 tab: {233 flex: 1,234 paddingVertical: 12,235 alignItems: 'center',236 borderBottomWidth: 2,237 borderBottomColor: '#2196F3',238 },239 tabText: {240 fontSize: 16,241 fontWeight: '500',242 color: '#2196F3',243 },244 listContainer: {245 flex: 1,246 backgroundColor: 'white',247 margin: 16,248 borderRadius: 8,249 overflow: 'hidden',250 },251 listTitle: {252 fontSize: 18,253 fontWeight: 'bold',254 padding: 16,255 backgroundColor: '#f8f9fa',256 borderBottomWidth: 1,257 borderBottomColor: '#e9ecef',258 },259 userItem: {260 flexDirection: 'row',261 alignItems: 'center',262 padding: 16,263 backgroundColor: 'white',264 },265 avatar: {266 width: 50,267 height: 50,268 borderRadius: 25,269 marginRight: 12,270 },271 userInfo: {272 flex: 1,273 },274 userName: {275 fontSize: 16,276 fontWeight: '500',277 color: '#333',278 marginBottom: 4,279 },280 userEmail: {281 fontSize: 14,282 color: '#666',283 marginBottom: 2,284 },285 userAge: {286 fontSize: 12,287 color: '#999',288 },289 indexBadge: {290 backgroundColor: '#2196F3',291 borderRadius: 12,292 width: 24,293 height: 24,294 justifyContent: 'center',295 alignItems: 'center',296 },297 indexText: {298 color: 'white',299 fontSize: 12,300 fontWeight: 'bold',301 },302 separator: {303 height: 1,304 backgroundColor: '#f0f0f0',305 marginLeft: 78,306 },307 sectionHeader: {308 flexDirection: 'row',309 alignItems: 'center',310 backgroundColor: '#e3f2fd',311 paddingHorizontal: 16,312 paddingVertical: 8,313 },314 sectionHeaderText: {315 fontSize: 16,316 fontWeight: 'bold',317 color: '#1976d2',318 },319 sectionCount: {320 fontSize: 14,321 color: '#666',322 marginLeft: 8,323 },324 sectionSeparator: {325 height: 8,326 backgroundColor: '#f5f5f5',327 },328 loadingFooter: {329 flexDirection: 'row',330 justifyContent: 'center',331 alignItems: 'center',332 paddingVertical: 16,333 },334 loadingText: {335 marginLeft: 8,336 fontSize: 14,337 color: '#666',338 },339 emptyContainer: {340 flex: 1,341 justifyContent: 'center',342 alignItems: 'center',343 paddingVertical: 50,344 },345 emptyText: {346 fontSize: 16,347 color: '#999',348 marginBottom: 16,349 },350 retryButton: {351 backgroundColor: '#2196F3',352 paddingHorizontal: 24,353 paddingVertical: 12,354 borderRadius: 6,355 },356 retryButtonText: {357 color: 'white',358 fontSize: 14,359 fontWeight: '500',360 },361});362363export default ListComponentsExample;
评论