乐闻世界logo
搜索文章和话题

Expo

Expo是一个面向Android、iOS和网页应用的开源框架。Expo汇集了移动和网络的精华,为构建和扩展应用程序提供了许多重要的功能。Expo npm包为React Native应用程序提供了一套令人难以置信的功能。
Expo
查看更多相关内容
什么是Expo EAS?它包含哪些核心服务?Expo EAS (Expo Application Services) 是Expo官方提供的一套云服务,用于简化Expo应用的构建、提交和更新流程。EAS提供了从开发到部署的完整解决方案。 **EAS核心服务:** 1. **EAS Build(构建服务)** EAS Build是云端构建服务,可以构建Android APK/IPA和iOS IPA文件。 **主要功能:** - 云端构建,无需本地配置原生环境 - 支持开发和生产两种构建配置 - 自动处理签名和证书 - 构建历史记录和日志查看 - 并行构建支持 **使用方法:** ```bash # 安装EAS CLI npm install -g eas-cli # 配置EAS eas build:configure # 构建Android应用 eas build --platform android # 构建iOS应用(需要Apple开发者账号) eas build --platform ios # 构建开发版本 eas build --profile development --platform android ``` 2. **EAS Submit(提交服务)** EAS Submit自动将构建好的应用提交到应用商店。 **支持的平台:** - Google Play Store - Apple App Store **使用方法:** ```bash # 提交到Google Play eas submit --platform android --latest # 提交到App Store eas submit --platform ios --latest ``` 3. **EAS Update(更新服务)** EAS Update允许通过OTA (Over-the-Air)方式更新应用,无需重新提交应用商店。 **主要功能:** - 即时推送更新 - 支持回滚到之前版本 - 细粒度的更新控制 - 更新分组和发布策略 **使用方法:** ```bash # 创建更新 eas update --branch production --message "Fix bug" # 查看更新历史 eas update:list # 回滚更新 eas update:rollback --branch production ``` **EAS配置文件:** 在项目根目录创建`eas.json`配置文件: ```json { "cli": { "version": ">= 5.2.0" }, "build": { "development": { "developmentClient": true, "distribution": "internal" }, "preview": { "distribution": "internal", "android": { "buildType": "apk" } }, "production": { "android": { "buildType": "app-bundle" }, "ios": { "autoIncrement": true } } }, "submit": { "production": { "android": { "serviceAccountKeyPath": "./google-service-account.json" }, "ios": { "appleId": "your-apple-id@email.com", "ascAppId": "YOUR_APP_STORE_CONNECT_APP_ID", "appleTeamId": "YOUR_TEAM_ID" } } } } ``` **环境变量管理:** EAS支持在构建时注入环境变量: ```bash # 设置环境变量 eas secret:create --name API_KEY --value "your-api-key" # 在代码中使用 const apiKey = process.env.API_KEY; ``` **最佳实践:** 1. **CI/CD集成**:将EAS Build集成到GitHub Actions或其他CI/CD流程中 2. **版本管理**:使用Git分支和EAS Update分支对应管理不同环境 3. **构建优化**:合理配置构建配置,区分开发和生产环境 4. **监控和日志**:定期查看构建日志,及时发现和解决问题 5. **权限管理**:为团队成员分配适当的EAS权限 **限制和注意事项:** - iOS构建需要Apple开发者账号和付费开发者计划 - 构建时间取决于项目大小和服务器负载 - 免费账户有构建次数限制 - 某些原生功能可能需要额外配置 EAS大大简化了Expo应用的部署流程,使开发者能够更专注于应用开发本身。
前端 · 2月21日 16:08
Expo CLI和Expo Go有什么区别?它们如何协同工作?Expo CLI和Expo Go是Expo开发流程中的两个核心工具,它们各自承担不同的职责,协同工作以提供高效的开发体验。 **Expo CLI:** Expo CLI是命令行工具,用于创建、构建和管理Expo项目。 **主要功能:** 1. **项目初始化**:通过`npx create-expo-app`命令快速创建新的Expo项目,支持TypeScript、JavaScript等多种模板。 2. **开发服务器**:启动开发服务器,实时编译代码并提供热重载功能。 3. **构建配置**:配置和管理项目的构建设置,包括应用图标、启动画面、权限配置等。 4. **打包发布**:支持构建APK、IPA等安装包,或直接发布到Expo服务器。 5. **依赖管理**:安装和更新Expo SDK版本及依赖包。 **常用命令:** ```bash npx create-expo-app my-app npx expo start npx expo build:android npx expo build:ios ``` **Expo Go:** Expo Go是一个移动应用,可在Android和iOS设备上安装,用于实时预览和测试Expo应用。 **主要功能:** 1. **实时预览**:通过扫描二维码或输入URL,在真实设备上查看应用效果。 2. **无需构建**:开发过程中无需编译原生代码,大幅提升开发效率。 3. **跨设备测试**:同时在多台设备上测试应用,验证不同屏幕尺寸和系统版本的兼容性。 4. **内置SDK**:包含完整的Expo SDK,支持所有Expo组件和API。 **工作流程:** 1. 使用Expo CLI创建项目并启动开发服务器 2. 在移动设备上安装Expo Go应用 3. 通过Expo Go连接到开发服务器 4. 实时查看代码修改效果 **限制:** Expo Go不支持自定义原生代码,如果项目需要添加自定义原生模块,需要使用Expo Development Build或Eject流程。 **最佳实践:** - 开发阶段优先使用Expo Go进行快速迭代 - 测试阶段使用Development Build获得更接近生产环境的表现 - 生产构建使用EAS Build生成优化的安装包 这两个工具的结合使得Expo开发流程既快速又灵活,适合从原型到生产的完整开发周期。
前端 · 2月21日 16:06
Expo如何支持Web平台?有哪些注意事项?Expo支持Web平台,使开发者能够使用相同的代码库构建Web应用。这大大扩展了Expo的应用场景,实现了真正的跨平台开发。 **Expo for Web特点:** 1. **单一代码库**:使用相同的JavaScript/TypeScript代码 2. **响应式设计**:自动适应不同屏幕尺寸 3. **Web API支持**:访问浏览器原生API 4. **PWA支持**:可配置为渐进式Web应用 5. **快速开发**:支持热重载和快速刷新 **配置Web支持:** 1. **安装依赖:** ```bash npx expo install react-dom react-native-web @expo/webpack-config ``` 2. **配置app.json:** ```json { "expo": { "web": { "bundler": "webpack", "output": "single", "favicon": "./assets/favicon.png" }, "experiments": { "typedRoutes": true } } } ``` 3. **启动Web开发服务器:** ```bash npx expo start --web ``` **平台特定代码:** 使用`Platform`模块处理平台差异: ```typescript import { Platform } from 'react-native'; function MyComponent() { if (Platform.OS === 'web') { return <div>Web specific content</div>; } return <View>Mobile specific content</View>; } ``` **Web特定API:** 1. **窗口API:** ```typescript // 获取窗口尺寸 const width = window.innerWidth; const height = window.innerHeight; // 监听窗口大小变化 window.addEventListener('resize', handleResize); ``` 2. **本地存储:** ```typescript // 使用localStorage localStorage.setItem('key', 'value'); const value = localStorage.getItem('key'); ``` 3. **导航API:** ```typescript // 使用浏览器历史 window.history.pushState({}, '', '/new-route'); window.history.back(); ``` **样式适配:** 1. **响应式样式:** ```typescript import { StyleSheet, Dimensions } from 'react-native'; const styles = StyleSheet.create({ container: { width: Dimensions.get('window').width > 768 ? '80%' : '100%', padding: 16, }, }); ``` 2. **CSS媒体查询:** ```typescript // 使用expo-linear-gradient等库 import { LinearGradient } from 'expo-linear-gradient'; <LinearGradient colors={['#4c669f', '#3b5998']} style={{ flex: 1 }} /> ``` **Web特定组件:** 1. **HTML元素:** ```typescript // 在Web上使用HTML元素 import { View, Text } from 'react-native'; // 在Web上渲染为div和span <View style={{ padding: 16 }}> <Text>Hello Web</Text> </View> ``` 2. **Web特定库:** ```typescript // 使用react-web-specific库 import { useMediaQuery } from 'react-responsive'; const isDesktop = useMediaQuery({ minWidth: 992 }); ``` **性能优化:** 1. **代码分割:** ```typescript // 使用React.lazy进行代码分割 const LazyComponent = React.lazy(() => import('./LazyComponent')); ``` 2. **懒加载:** ```typescript // 懒加载图片 import { Image } from 'react-native'; <Image source={{ uri: 'https://example.com/image.jpg' }} loading="lazy" /> ``` 3. **缓存策略:** ```typescript // 配置Service Worker进行缓存 // 在public/sw.js中配置 ``` **PWA配置:** 1. **创建manifest.json:** ```json { "name": "My Expo App", "short_name": "MyApp", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#000000", "icons": [ { "src": "/assets/icon-192.png", "sizes": "192x192", "type": "image/png" } ] } ``` 2. **配置Service Worker:** ```javascript // public/sw.js self.addEventListener('install', event => { event.waitUntil( caches.open('v1').then(cache => { return cache.addAll([ '/', '/index.html', '/static/js/main.js' ]); }) ); }); ``` **部署Web应用:** 1. **构建生产版本:** ```bash npx expo export:web ``` 2. **部署到Vercel:** ```bash # 安装Vercel CLI npm i -g vercel # 部署 vercel ``` 3. **部署到Netlify:** ```bash # 安装Netlify CLI npm i -g netlify-cli # 部署 netlify deploy --prod ``` **常见问题:** 1. **样式差异**:Web和移动端样式可能有所不同,需要测试和调整 2. **API兼容性**:某些移动端API在Web上不可用,需要提供替代方案 3. **性能问题**:Web版本可能比移动端慢,需要优化加载和渲染 4. **触摸事件**:Web需要同时支持鼠标和触摸事件 5. **键盘导航**:Web需要支持键盘导航和无障碍访问 **最佳实践:** 1. **渐进增强**:先实现核心功能,然后为Web添加特定优化 2. **响应式设计**:确保应用在不同屏幕尺寸上都能良好显示 3. **性能监控**:使用Web性能工具监控和优化加载速度 4. **SEO优化**:添加meta标签和结构化数据 5. **测试覆盖**:在多个浏览器和设备上测试Web版本 Expo for Web使开发者能够用一套代码构建真正的跨平台应用,大大提高了开发效率和代码复用率。
前端 · 2月21日 16:04
Expo有哪些常用的开发工具和调试技巧?Expo提供了丰富的开发工具和调试功能,帮助开发者提高开发效率和代码质量。掌握这些工具对于Expo开发至关重要。 **核心开发工具:** 1. **Expo CLI** Expo CLI是主要的命令行工具,提供项目创建、开发服务器启动、构建等功能。 **常用命令:** ```bash # 创建新项目 npx create-expo-app my-app # 启动开发服务器 npx expo start # 清除缓存 npx expo start -c # 查看设备信息 npx expo start --tunnel # 生成应用图标 npx expo install expo-app-icon ``` 2. **Expo Dev Tools** Expo Dev Tools是基于Web的开发工具界面,提供可视化的项目管理功能。 **功能:** - 设备连接管理 - 日志查看 - 性能监控 - 快速刷新控制 - 网络请求监控 3. **React Native Debugger** React Native Debugger是一个独立的调试工具,集成了React DevTools和Redux DevTools。 **使用方法:** ```bash # 安装 npm install -g react-native-debugger # 启动 react-native-debugger ``` **调试技巧:** 1. **Console调试** 使用console.log输出调试信息: ```typescript console.log('Debug info'); console.warn('Warning message'); console.error('Error message'); // 使用console.group组织日志 console.group('User Data'); console.log('Name:', user.name); console.log('Age:', user.age); console.groupEnd(); ``` 2. **React DevTools** React DevTools提供组件树查看、props检查、状态监控等功能。 **使用方法:** ```typescript // 在开发环境中启用 if (__DEV__) { const DevTools = require('react-devtools'); DevTools.connectToDevTools({ host: 'localhost', port: 8097, }); } ``` 3. **Flipper** Flipper是Facebook开发的移动应用调试工具,支持网络请求、数据库、布局检查等。 **配置Flipper:** ```javascript // 在metro.config.js中 const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); module.exports = config; ``` 4. **Reactotron** Reactotron是一个强大的调试工具,支持Redux、API调用、日志等功能。 **配置Reactotron:** ```typescript import Reactotron from 'reactotron-react-native'; if (__DEV__) { const tron = Reactotron .configure() .useReactNative() .connect(); console.tron = tron; } ``` **性能优化工具:** 1. **性能监控** 使用React Native Performance API: ```typescript import { Performance } from 'react-native'; // 记录性能标记 Performance.mark('component-start'); // 组件渲染完成后 Performance.mark('component-end'); // 测量性能 Performance.measure('component-render', 'component-start', 'component-end'); ``` 2. **内存分析** 使用Flipper或React Native Debugger查看内存使用情况: - 监控内存泄漏 - 分析组件渲染性能 - 优化图片和资源加载 3. **Bundle分析** 使用webpack-bundle-analyzer分析bundle大小: ```bash npm install --save-dev @expo/webpack-config webpack-bundle-analyzer ``` **错误处理:** 1. **错误边界** 使用React错误边界捕获组件错误: ```typescript class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error('Error caught by boundary:', error, errorInfo); } render() { if (this.state.hasError) { return <Text>Something went wrong.</Text>; } return this.props.children; } } ``` 2. **全局错误处理** 监听全局错误事件: ```typescript // JavaScript错误 const defaultErrorHandler = ErrorUtils.getGlobalHandler(); ErrorUtils.setGlobalHandler((error, isFatal) => { console.error('Global error:', error); defaultErrorHandler(error, isFatal); }); // Promise错误 process.on('unhandledRejection', (error) => { console.error('Unhandled promise rejection:', error); }); ``` **测试工具:** 1. **Jest** Jest是Expo默认的测试框架: ```typescript // 示例测试 import { render } from '@testing-library/react-native'; import MyComponent from './MyComponent'; test('renders correctly', () => { const { getByText } = render(<MyComponent />); expect(getByText('Hello')).toBeTruthy(); }); ``` 2. **Detox** Detox是端到端测试框架: ```typescript describe('Login Flow', () => { it('should login successfully', async () => { await element(by.id('username')).typeText('user@example.com'); await element(by.id('password')).typeText('password'); await element(by.id('login-button')).tap(); await expect(element(by.id('welcome'))).toBeVisible(); }); }); ``` **最佳实践:** 1. **开发环境配置**:使用不同的环境变量配置开发、测试和生产环境 2. **日志管理**:在生产环境中禁用详细的日志输出 3. **错误追踪**:集成Sentry或Bugsnag等错误追踪服务 4. **性能监控**:定期监控应用性能指标 5. **自动化测试**:建立完善的测试体系 掌握这些调试工具和技巧,可以大大提高Expo开发的效率和质量。
前端 · 2月21日 16:04
Expo提供了哪些常用的原生组件和API?如何使用它们?Expo提供了丰富的原生组件和API,使开发者能够轻松访问移动设备的各种功能。这些组件和API经过精心设计,提供了统一的跨平台接口。 **核心原生组件:** 1. **Camera(相机)** ```javascript import { Camera } from 'expo-camera'; // 请求相机权限 const { status } = await Camera.requestCameraPermissionsAsync(); // 使用相机组件 <Camera style={{ flex: 1 }} type={type} /> ``` 2. **Location(位置服务)** ```javascript import * as Location from 'expo-location'; // 请求位置权限 const { status } = await Location.requestForegroundPermissionsAsync(); // 获取当前位置 const location = await Location.getCurrentPositionAsync({}); ``` 3. **Notifications(推送通知)** ```javascript import * as Notifications from 'expo-notifications'; // 请求通知权限 const { status } = await Notifications.requestPermissionsAsync(); // 发送本地通知 await Notifications.scheduleNotificationAsync({ content: { title: 'Hello!', body: 'This is a notification', }, trigger: { seconds: 2 }, }); ``` 4. **Audio(音频)** ```javascript import { Audio } from 'expo-av'; // 播放音频 const { sound } = await Audio.Sound.createAsync( { uri: 'https://example.com/audio.mp3' } ); await sound.playAsync(); ``` 5. **FileSystem(文件系统)** ```javascript import * as FileSystem from 'expo-file-system'; // 读取文件 const content = await FileSystem.readAsStringAsync(fileUri); // 写入文件 await FileSystem.writeAsStringAsync(fileUri, 'Hello World'); ``` 6. **ImagePicker(图片选择器)** ```javascript import * as ImagePicker from 'expo-image-picker'; // 选择图片 const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], allowsEditing: true, }); ``` 7. **SecureStore(安全存储)** ```javascript import * as SecureStore from 'expo-secure-store'; // 保存敏感数据 await SecureStore.setItemAsync('token', 'user-token'); // 读取敏感数据 const token = await SecureStore.getItemAsync('token'); ``` 8. **Sensors(传感器)** ```javascript import { Accelerometer } from 'expo-sensors'; // 监听加速度计 Accelerometer.addListener(accelerometerData => { console.log(accelerometerData); }); ``` **常用API:** 1. **Constants(常量)** ```javascript import Constants from 'expo-constants'; // 获取设备信息 const deviceName = Constants.deviceName; const platform = Constants.platform; ``` 2. **Haptics(触觉反馈)** ```javascript import * as Haptics from 'expo-haptics'; // 触发触觉反馈 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); ``` 3. **Linking(链接处理)** ```javascript import * as Linking from 'expo-linking'; // 打开URL await Linking.openURL('https://expo.dev'); // 处理深度链接 const url = await Linking.getInitialURL(); ``` 4. **ScreenOrientation(屏幕方向)** ```javascript import * as ScreenOrientation from 'expo-screen-orientation'; // 锁定屏幕方向 await ScreenOrientation.lockAsync( ScreenOrientation.OrientationLock.PORTRAIT ); ``` **权限管理:** Expo提供了统一的权限请求API: ```javascript import * as Permissions from 'expo-permissions'; // 请求权限 const { status, granted } = await Permissions.askAsync( Permissions.CAMERA, Permissions.LOCATION ); ``` **最佳实践:** 1. **权限请求时机**:在用户需要使用功能时再请求权限,提供清晰的说明 2. **错误处理**:妥善处理权限被拒绝的情况,提供友好的用户提示 3. **性能优化**:及时释放资源,如停止传感器监听、取消音频播放等 4. **平台差异**:注意不同平台的API差异,使用条件渲染处理平台特定功能 5. **异步操作**:所有原生API调用都是异步的,使用async/await处理 这些原生组件和API大大简化了跨平台开发,使开发者能够专注于业务逻辑而不是原生实现细节。
前端 · 2月21日 16:03
Expo应用的可访问性(Accessibility)如何实现?有哪些最佳实践?Expo应用的可访问性(Accessibility)是确保所有用户,包括有视觉、听觉、运动或认知障碍的用户,都能有效使用应用的重要方面。Expo和React Native提供了丰富的可访问性API和属性。 **可访问性基础:** 1. **accessibilityLabel** 为屏幕阅读器提供元素的描述。 ```typescript <Image source={{ uri: 'https://example.com/image.jpg' }} accessibilityLabel="用户头像" style={{ width: 50, height: 50 }} /> <Button title="提交" accessibilityLabel="提交表单" onPress={handleSubmit} /> ``` 2. **accessibilityHint** 提供有关元素行为的额外信息。 ```typescript <TouchableOpacity accessibilityLabel="查看详情" accessibilityHint="点击查看用户详细信息" onPress={handlePress} > <Text>查看</Text> </TouchableOpacity> ``` 3. **accessibilityRole** 指定元素的UI角色。 ```typescript <View accessibilityRole="button" accessibilityLabel="确认" onClick={handleConfirm} > <Text>确认</Text> </View> ``` **常用可访问性角色:** - `button`:按钮 - `link`:链接 - `header`:标题 - `text`:文本 - `image`:图片 - `search`:搜索框 - `adjustable`:可调节控件 4. **accessibilityState** 描述元素的当前状态。 ```typescript <CheckBox accessibilityLabel="同意条款" accessibilityState={{ checked: isChecked, disabled: false, }} value={isChecked} onValueChange={setIsChecked} /> ``` **可访问性状态:** - `disabled`:禁用状态 - `selected`:选中状态 - `checked`:勾选状态 - `busy`:忙碌状态 - `expanded`:展开状态 5. **accessibilityValue** 描述元素的值。 ```typescript <Slider accessibilityLabel="音量" accessibilityValue={{ min: 0, max: 100, now: volume }} value={volume} onValueChange={setVolume} /> <ProgressBar accessibilityLabel="下载进度" accessibilityValue={{ min: 0, max: 100, now: progress }} progress={progress / 100} /> ``` **可访问性操作:** 1. **accessibilityActions** 定义元素支持的可访问性操作。 ```typescript <View accessibilityLabel="播放控制" accessibilityActions={[ { name: 'increment', label: '增加音量' }, { name: 'decrement', label: '减少音量' }, { name: 'magicTap', label: '双击播放/暂停' }, ]} onAccessibilityAction={(event) => { switch (event.nativeEvent.actionName) { case 'increment': setVolume((v) => Math.min(v + 10, 100)); break; case 'decrement': setVolume((v) => Math.max(v - 10, 0)); break; case 'magicTap': togglePlayPause(); break; } }} > <Text>音量: {volume}</Text> </View> ``` 2. **onAccessibilityEscape** 定义转义操作。 ```typescript <Modal visible={isVisible} onAccessibilityEscape={() => setIsVisible(false)} accessibilityViewIsModal={true} > <View> <Text>模态框内容</Text> <Button title="关闭" onPress={() => setIsVisible(false)} /> </View> </Modal> ``` **可访问性属性:** 1. **accessible** 标记元素为可访问的。 ```typescript <View accessible={true}> <Text>这个视图可以被屏幕阅读器访问</Text> </View> ``` 2. **accessibilityElementsHidden** 隐藏子元素的可访问性。 ```typescript <View accessible={true} accessibilityLabel="容器" accessibilityElementsHidden={isHidden} > <Text>子元素1</Text> <Text>子元素2</Text> </View> ``` 3. **accessibilityIgnoresInvertColors** 忽略颜色反转设置。 ```typescript <Image source={{ uri: 'https://example.com/chart.jpg' }} accessibilityIgnoresInvertColors={true} style={{ width: 200, height: 200 }} /> ``` **焦点管理:** 1. **focusable** 使元素可聚焦。 ```typescript <TextInput focusable={true} accessibilityLabel="用户名输入框" placeholder="请输入用户名" /> ``` 2. **accessibilityLiveRegion** 标记动态内容区域。 ```typescript <Text accessibilityLiveRegion="polite" accessibilityLabel="状态信息" > {statusMessage} </Text> ``` **可访问性事件:** ```typescript function useAccessibilityFocus() { const [isFocused, setIsFocused] = useState(false); const handleFocus = () => { setIsFocused(true); console.log('Element focused'); }; const handleBlur = () => { setIsFocused(false); console.log('Element blurred'); }; return { isFocused, handleFocus, handleBlur }; } ``` **语义化组件:** 1. **使用语义化HTML标签(Web)** ```typescript // 在Web平台上使用语义化标签 if (Platform.OS === 'web') { return ( <nav accessibilityRole="navigation"> <ul> <li><a href="/home">首页</a></li> <li><a href="/about">关于</a></li> </ul> </nav> ); } ``` 2. **使用正确的可访问性角色** ```typescript // 按钮使用button角色 <TouchableOpacity accessibilityRole="button" accessibilityLabel="提交" onPress={handleSubmit} > <Text>提交</Text> </TouchableOpacity> // 链接使用link角色 <TouchableOpacity accessibilityRole="link" accessibilityLabel="查看详情" onPress={handlePress} > <Text>查看详情</Text> </TouchableOpacity> ``` **最佳实践:** 1. **提供清晰的标签** ```typescript // 好的实践 <Button title="提交表单" accessibilityLabel="提交用户注册表单" onPress={handleSubmit} /> // 避免重复 <Button title="提交" accessibilityLabel="提交" // 与title重复 onPress={handleSubmit} /> ``` 2. **使用有意义的提示** ```typescript <TouchableOpacity accessibilityLabel="删除项目" accessibilityHint="此操作无法撤销,请谨慎操作" onPress={handleDelete} > <Text>删除</Text> </TouchableOpacity> ``` 3. **支持键盘导航** ```typescript function KeyboardNavigation() { const [focusedIndex, setFocusedIndex] = useState(0); const handleKeyDown = (event) => { if (event.key === 'ArrowDown') { setFocusedIndex((i) => Math.min(i + 1, items.length - 1)); } else if (event.key === 'ArrowUp') { setFocusedIndex((i) => Math.max(i - 1, 0)); } else if (event.key === 'Enter') { items[focusedIndex].onPress(); } }; return ( <View onKeyDown={handleKeyDown}> {items.map((item, index) => ( <TouchableOpacity key={index} accessibilityLabel={item.label} focusable={true} style={[ styles.item, focusedIndex === index && styles.focused, ]} onPress={item.onPress} > <Text>{item.label}</Text> </TouchableOpacity> ))} </View> ); } ``` 4. **支持屏幕阅读器** ```typescript function ScreenReaderSupport() { const isScreenReaderEnabled = useAccessibilityInfo(); return ( <View> {isScreenReaderEnabled ? ( <Text>屏幕阅读器已启用</Text> ) : ( <Text>屏幕阅读器未启用</Text> )} </View> ); } ``` 5. **测试可访问性** ```typescript import { AccessibilityInfo } from 'react-native'; async function testAccessibility() { // 检查屏幕阅读器是否启用 const isScreenReaderEnabled = await AccessibilityInfo.isScreenReaderEnabled(); console.log('Screen reader enabled:', isScreenReaderEnabled); // 检查减少动画设置 const isReduceMotionEnabled = await AccessibilityInfo.isReduceMotionEnabled(); console.log('Reduce motion enabled:', isReduceMotionEnabled); // 监听可访问性变化 AccessibilityInfo.addEventListener( 'screenReaderChanged', (isEnabled) => { console.log('Screen reader changed:', isEnabled); } ); } ``` **可访问性工具:** 1. **AccessibilityInfo API** ```typescript import { AccessibilityInfo } from 'react-native'; // 获取可访问性信息 const isScreenReaderEnabled = await AccessibilityInfo.isScreenReaderEnabled(); const isReduceMotionEnabled = await AccessibilityInfo.isReduceMotionEnabled(); // 监听变化 AccessibilityInfo.addEventListener('screenReaderChanged', (isEnabled) => { console.log('Screen reader:', isEnabled); }); AccessibilityInfo.addEventListener('reduceMotionChanged', (isEnabled) => { console.log('Reduce motion:', isEnabled); }); ``` 2. **可访问性检查工具** - iOS:VoiceOver - Android:TalkBack - Web:屏幕阅读器(NVDA、JAWS等) **常见可访问性问题:** 1. **缺少可访问性标签** - 为所有交互元素添加accessibilityLabel - 为图片提供描述性标签 2. **焦点管理不当** - 确保键盘导航顺序合理 - 提供清晰的焦点指示器 3. **颜色对比度不足** - 确保文本和背景有足够的对比度 - 支持高对比度模式 4. **动态内容未通知** - 使用accessibilityLiveRegion标记动态内容 - 及时通知屏幕阅读器内容变化 通过实施这些可访问性实践,可以确保Expo应用对所有用户都是友好和可用的。
前端 · 2月21日 16:03
什么是Expo Router?它如何实现文件系统路由?Expo Router是Expo官方提供的路由解决方案,基于文件系统路由,专为Expo应用设计。它简化了导航管理,提供了类型安全的路由和深度链接支持。 **核心特性:** 1. **文件系统路由** - 基于文件和文件夹结构自动生成路由 - 支持动态路由和嵌套路由 - 类似Next.js的路由体验 2. **类型安全** - 自动生成TypeScript类型 - 编译时路由检查 - 智能代码补全 3. **深度链接** - 原生深度链接支持 - Web URL兼容 - 自动处理链接参数 **安装和配置:** ```bash # 安装Expo Router npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar # 配置app.json { "expo": { "scheme": "myapp", "experiments": { "typedRoutes": true } } } ``` **项目结构:** ``` app/ ├── _layout.tsx # 根布局 ├── index.tsx # 首页 (/) ├── about.tsx # 关于页面 (/about) ├── user/ │ ├── [id].tsx # 用户详情页 (/user/:id) │ └── settings.tsx # 用户设置 (/user/settings) ├── (tabs)/ │ ├── _layout.tsx # Tab布局 │ ├── home.tsx # Tab首页 │ └── profile.tsx # Tab个人页 └── (modal)/ └── _layout.tsx # Modal布局 ``` **路由类型:** 1. **静态路由** ```typescript // app/index.tsx export default function HomeScreen() { return <Text>Home</Text>; } ``` 2. **动态路由** ```typescript // app/user/[id].tsx import { useLocalSearchParams } from 'expo-router'; export default function UserScreen() { const { id } = useLocalSearchParams<{ id: string }>(); return <Text>User: {id}</Text>; } ``` 3. **嵌套路由** ```typescript // app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; export default function TabLayout() { return ( <Tabs> <Tabs.Screen name="home" options={{ title: 'Home' }} /> <Tabs.Screen name="profile" options={{ title: 'Profile' }} /> </Tabs> ); } ``` 4. **分组路由** ```typescript // (tabs)和(modal)是路由组,不影响URL // app/(tabs)/home.tsx -> /home // app/(modal)/settings.tsx -> /settings ``` **导航API:** ```typescript import { useRouter, useSegments } from 'expo-router'; function MyComponent() { const router = useRouter(); const segments = useSegments(); // 导航到页面 const navigateToUser = () => { router.push('/user/123'); }; // 替换当前页面 const replacePage = () => { router.replace('/settings'); }; // 返回上一页 const goBack = () => { router.back(); }; // 检查当前路由 const isHome = segments[0] === 'home'; return ( <View> <Button title="Go to User" onPress={navigateToUser} /> <Button title="Replace" onPress={replacePage} /> <Button title="Back" onPress={goBack} /> </View> ); } ``` **链接API:** ```typescript import { Link, useLocalSearchParams } from 'expo-router'; // 使用Link组件 <Link href="/user/123"> <Text>Go to User</Text> </Link> // 使用useLocalSearchParams获取参数 const { id } = useLocalSearchParams<{ id: string }>(); ``` **深度链接配置:** ```typescript // app/_layout.tsx import { Stack } from 'expo-router'; import * as Linking from 'expo-linking'; const linking = { prefixes: [Linking.createURL('/')], config: { screens: { index: '/', user: '/user/:id', }, }, }; export default function RootLayout() { return <Stack />; } ``` **最佳实践:** 1. **路由组织**:合理使用路由组和嵌套布局,保持结构清晰 2. **类型安全**:启用typedRoutes实验性功能,获得完整的类型支持 3. **性能优化**:使用懒加载减少初始包大小 4. **错误处理**:实现404页面和错误边界 5. **测试覆盖**:为路由逻辑编写单元测试 **与React Navigation的对比:** Expo Router基于React Navigation构建,提供了更高级的抽象: - 更简单的配置 - 自动类型生成 - 文件系统路由 - 更好的深度链接支持 Expo Router是构建Expo应用导航的理想选择,特别适合需要类型安全和深度链接的项目。
前端 · 2月21日 16:03
Expo中如何实现动画效果?有哪些常用的动画库?Expo动画是提升用户体验的重要手段。Expo支持多种动画库和API,从简单的过渡效果到复杂的交互动画都有完善的解决方案。 **动画库选择:** 1. **React Native Animated API** React Native内置的动画API,适合大多数动画需求。 **基础动画:** ```typescript import { Animated, Easing } from 'react-native'; function FadeInComponent() { const fadeAnim = useRef(new Animated.Value(0)).current; useEffect(() => { Animated.timing(fadeAnim, { toValue: 1, duration: 1000, easing: Easing.ease, useNativeDriver: true, }).start(); }, []); return ( <Animated.View style={{ opacity: fadeAnim }}> <Text>Fade In</Text> </Animated.View> ); } ``` **插值动画:** ```typescript function InterpolationComponent() { const translateX = useRef(new Animated.Value(0)).current; const rotate = translateX.interpolate({ inputRange: [0, 100], outputRange: ['0deg', '180deg'], }); const scale = translateX.interpolate({ inputRange: [0, 100], outputRange: [1, 2], }); return ( <Animated.View style={{ transform: [ { translateX }, { rotate }, { scale }, ], }} > <Text>Animated Box</Text> </Animated.View> ); } ``` **并行和序列动画:** ```typescript function ComplexAnimation() { const fadeAnim = useRef(new Animated.Value(0)).current; const scaleAnim = useRef(new Animated.Value(1)).current; const startAnimation = () => { Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true, }), Animated.spring(scaleAnim, { toValue: 1.5, friction: 5, useNativeDriver: true, }), ]).start(); }; return ( <Animated.View style={{ opacity: fadeAnim, transform: [{ scale: scaleAnim }], }} > <Button title="Animate" onPress={startAnimation} /> </Animated.View> ); } ``` 2. **React Native Reanimated** 更强大的动画库,支持手势和复杂动画。 **安装:** ```bash npx expo install react-native-reanimated ``` **基础动画:** ```typescript import Animated, { useSharedValue, useAnimatedStyle, withTiming, withSpring, withSequence, } from 'react-native-reanimated'; function ReanimatedComponent() { const opacity = useSharedValue(0); const scale = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => { return { opacity: opacity.value, transform: [{ scale: scale.value }], }; }); const startAnimation = () => { opacity.value = withTiming(1, { duration: 500 }); scale.value = withSpring(1.5); }; return ( <Animated.View style={animatedStyle}> <Button title="Animate" onPress={startAnimation} /> </Animated.View> ); } ``` **手势动画:** ```typescript import { Gesture, GestureDetector } from 'react-native-gesture-handler'; function GestureComponent() { const translateX = useSharedValue(0); const translateY = useSharedValue(0); const pan = Gesture.Pan() .onUpdate((event) => { translateX.value = event.translationX; translateY.value = event.translationY; }) .onEnd(() => { translateX.value = withSpring(0); translateY.value = withSpring(0); }); const animatedStyle = useAnimatedStyle(() => { return { transform: [ { translateX: translateX.value }, { translateY: translateY.value }, ], }; }); return ( <GestureDetector gesture={pan}> <Animated.View style={animatedStyle}> <Text>Drag me</Text> </Animated.View> </GestureDetector> ); } ``` 3. **Lottie** 使用Adobe After Effects创建的复杂动画。 **安装:** ```bash npx expo install lottie-react-native ``` **使用Lottie动画:** ```typescript import LottieView from 'lottie-react-native'; function LottieComponent() { return ( <LottieView source={require('./assets/animation.json')} autoPlay loop style={{ width: 200, height: 200 }} /> ); } ``` **控制Lottie动画:** ```typescript function ControlledLottie() { const animationRef = useRef<LottieView>(null); const playAnimation = () => { animationRef.current?.play(); }; const pauseAnimation = () => { animationRef.current?.pause(); }; const resetAnimation = () => { animationRef.current?.reset(); }; return ( <View> <LottieView ref={animationRef} source={require('./assets/animation.json')} style={{ width: 200, height: 200 }} /> <Button title="Play" onPress={playAnimation} /> <Button title="Pause" onPress={pauseAnimation} /> <Button title="Reset" onPress={resetAnimation} /> </View> ); } ``` **常用动画模式:** 1. **淡入淡出** ```typescript function FadeInOut() { const opacity = useSharedValue(0); const fadeIn = () => { opacity.value = withTiming(1, { duration: 500 }); }; const fadeOut = () => { opacity.value = withTiming(0, { duration: 500 }); }; const style = useAnimatedStyle(() => ({ opacity: opacity.value, })); return ( <Animated.View style={style}> <Button title="Fade In" onPress={fadeIn} /> <Button title="Fade Out" onPress={fadeOut} /> </Animated.View> ); } ``` 2. **滑动动画** ```typescript function SlideAnimation() { const translateX = useSharedValue(-300); const slideIn = () => { translateX.value = withSpring(0); }; const slideOut = () => { translateX.value = withSpring(-300); }; const style = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], })); return ( <Animated.View style={style}> <Text>Slide Content</Text> </Animated.View> ); } ``` 3. **缩放动画** ```typescript function ScaleAnimation() { const scale = useSharedValue(1); const scaleUp = () => { scale.value = withSpring(1.5); }; const scaleDown = () => { scale.value = withSpring(1); }; const style = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], })); return ( <Animated.View style={style}> <Text>Scale Content</Text> </Animated.View> ); } ``` 4. **旋转动画** ```typescript function RotateAnimation() { const rotation = useSharedValue(0); const rotate = () => { rotation.value = withTiming(rotation.value + 360, { duration: 1000, easing: Easing.linear, }); }; const style = useAnimatedStyle(() => ({ transform: [{ rotate: `${rotation.value}deg` }], })); return ( <Animated.View style={style}> <Text>Rotate Content</Text> </Animated.View> ); } ``` **性能优化:** 1. **使用原生驱动** ```typescript // 使用useNativeDriver提高性能 Animated.timing(value, { toValue: 1, duration: 500, useNativeDriver: true, // 在UI线程运行 }).start(); ``` 2. **避免在动画中使用复杂布局** ```typescript // 避免在动画组件中使用flex布局 const style = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], // 避免使用flex相关的属性 })); ``` 3. **使用shouldComponentUpdate优化** ```typescript const AnimatedComponent = React.memo(({ value }) => { const animatedStyle = useAnimatedStyle(() => ({ opacity: value.value, })); return <Animated.View style={animatedStyle} />; }); ``` **最佳实践:** 1. **选择合适的动画库** - 简单动画:React Native Animated - 复杂动画和手势:React Native Reanimated - 设计师创建的动画:Lottie 2. **性能优先** - 使用原生驱动 - 避免在动画中使用复杂布局 - 使用useMemo和useCallback优化 3. **用户体验** - 提供流畅的过渡效果 - 避免过度动画 - 考虑性能较差的设备 4. **可访问性** - 为动画提供替代方案 - 尊重用户的减少动画偏好 - 提供动画控制选项 通过合理使用动画,可以显著提升Expo应用的用户体验和交互质量。
前端 · 2月21日 16:03
什么是Expo框架?它有哪些核心优势?Expo是一个基于React Native的开源框架,它简化了跨平台移动应用的开发流程。Expo提供了完整的开发工具链、预构建的原生组件和云服务,使开发者能够快速构建、测试和部署Android、iOS和Web应用。 **核心优势:** 1. **快速开发**:Expo提供了Expo CLI和Expo Go应用,开发者可以立即在真实设备上预览应用,无需配置复杂的原生开发环境。 2. **跨平台支持**:一次编写代码,即可在Android、iOS和Web平台运行,大幅减少开发成本和维护工作量。 3. **丰富的组件库**:Expo提供了超过40个预构建的原生组件,如相机、位置服务、推送通知等,无需编写原生代码即可使用。 4. **OTA更新**:支持通过Expo Over-the-Air更新机制,无需重新提交应用商店即可推送更新。 5. **云服务集成**:提供Expo Application Services (EAS),包括构建、提交、更新等服务。 **工作原理:** Expo在React Native之上构建了一个抽象层,提供了统一的API来访问原生功能。开发者使用JavaScript/TypeScript编写代码,Expo SDK处理与原生平台的交互。 **适用场景:** - 快速原型开发 - 中小型跨平台应用 - 需要快速迭代的项目 - 团队缺乏原生开发经验 **限制:** - 对于需要深度原生功能的应用可能不够灵活 - 应用包体积相对较大 - 某些高级功能需要使用Expo Development Build或Eject Expo持续更新,目前最新版本支持React Native的最新特性,并不断扩展功能和性能优化。
前端 · 2月21日 16:02
Expo的OTA更新是如何工作的?如何使用EAS Update?Expo的OTA (Over-the-Air) 更新功能允许开发者在不重新提交应用商店的情况下推送应用更新。这是一个强大的功能,可以显著加快迭代速度。 **OTA更新原理:** Expo OTA通过将JavaScript bundle和资源文件上传到Expo服务器,然后在应用启动时检查并下载更新来实现。 **工作流程:** 1. 开发者使用`eas update`命令上传更新 2. Expo服务器存储更新并分配唯一版本号 3. 应用启动时检查服务器是否有新版本 4. 如果有新版本,下载并应用更新 5. 用户下次打开应用时看到更新内容 **EAS Update使用方法:** 1. **安装EAS CLI:** ```bash npm install -g eas-cli ``` 2. **配置项目:** ```bash eas build:configure ``` 3. **创建更新:** ```bash # 创建并发布更新 eas update --branch production --message "Fix login bug" # 指定运行时版本 eas update --branch production --runtime-version 1.0.0 # 预览更新 eas update --branch preview --message "Test new feature" ``` 4. **查看更新历史:** ```bash # 查看所有更新 eas update:list # 查看特定分支的更新 eas update:list --branch production ``` 5. **回滚更新:** ```bash # 回滚到上一个版本 eas update:rollback --branch production # 回滚到特定版本 eas update:rollback --branch production --target-message "Previous stable version" ``` **配置app.json:** ```json { "expo": { "updates": { "url": "https://u.expo.dev/your-project-id" }, "runtimeVersion": { "policy": "appVersion" } } } ``` **运行时版本策略:** Expo支持多种运行时版本策略: 1. **appVersion策略**(推荐): ```json { "runtimeVersion": { "policy": "appVersion" } } ``` - 使用应用版本号作为运行时版本 - 简单直接,适合大多数场景 2. **nativeVersion策略**: ```json { "runtimeVersion": { "policy": "nativeVersion" } } ``` - 使用原生代码版本 - 更精确的控制 3. **customVersion策略**: ```json { "runtimeVersion": { "policy": "customVersion", "customVersion": "1.0.0" } } ``` - 自定义版本号 - 最大的灵活性 **更新分组:** 可以创建不同的更新分支来管理不同环境的更新: ```bash # 生产环境更新 eas update --branch production # 预览环境更新 eas update --branch preview # 开发环境更新 eas update --branch development ``` **客户端配置:** ```typescript import * as Updates from 'expo-updates'; // 检查是否有更新 async function checkForUpdates() { try { const update = await Updates.checkForUpdateAsync(); if (update.isAvailable) { await Updates.fetchUpdateAsync(); Updates.reloadAsync(); } } catch (error) { console.error('Error checking for updates:', error); } } // 手动触发更新检查 checkForUpdates(); // 监听更新事件 Updates.addListener(event => { if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) { console.log('Update available:', event); } }); ``` **限制和注意事项:** 1. **只能更新JavaScript和资源**:不能更新原生代码 2. **应用商店审核**:某些应用商店对OTA更新有政策限制 3. **版本兼容性**:更新必须与当前运行时版本兼容 4. **网络依赖**:用户需要网络连接才能接收更新 5. **回滚机制**:需要实现回滚策略以应对问题更新 **最佳实践:** 1. **渐进式发布**:先在小范围用户中测试更新 2. **版本管理**:保持清晰的版本号和更新日志 3. **错误监控**:监控更新后的错误率 4. **用户通知**:在更新前通知用户 5. **回滚准备**:随时准备回滚到稳定版本 6. **测试覆盖**:在发布前充分测试更新 **常见问题:** 1. **更新不生效**:检查运行时版本是否匹配 2. **更新失败**:查看服务器日志和客户端错误 3. **性能影响**:监控更新下载对应用性能的影响 4. **安全考虑**:确保更新传输使用HTTPS加密 Expo OTA更新是快速迭代应用的重要工具,合理使用可以大大提升开发效率和用户体验。
前端 · 2月21日 16:02