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

面试题手册

什么是Expo EAS?它包含哪些核心服务?

Expo EAS (Expo Application Services) 是Expo官方提供的一套云服务,用于简化Expo应用的构建、提交和更新流程。EAS提供了从开发到部署的完整解决方案。EAS核心服务:EAS Build(构建服务)EAS Build是云端构建服务,可以构建Android APK/IPA和iOS IPA文件。主要功能:云端构建,无需本地配置原生环境支持开发和生产两种构建配置自动处理签名和证书构建历史记录和日志查看并行构建支持使用方法:# 安装EAS CLInpm install -g eas-cli# 配置EASeas build:configure# 构建Android应用eas build --platform android# 构建iOS应用(需要Apple开发者账号)eas build --platform ios# 构建开发版本eas build --profile development --platform androidEAS Submit(提交服务)EAS Submit自动将构建好的应用提交到应用商店。支持的平台:Google Play StoreApple App Store使用方法:# 提交到Google Playeas submit --platform android --latest# 提交到App Storeeas submit --platform ios --latestEAS Update(更新服务)EAS Update允许通过OTA (Over-the-Air)方式更新应用,无需重新提交应用商店。主要功能:即时推送更新支持回滚到之前版本细粒度的更新控制更新分组和发布策略使用方法:# 创建更新eas update --branch production --message "Fix bug"# 查看更新历史eas update:list# 回滚更新eas update:rollback --branch productionEAS配置文件:在项目根目录创建eas.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支持在构建时注入环境变量:# 设置环境变量eas secret:create --name API_KEY --value "your-api-key"# 在代码中使用const apiKey = process.env.API_KEY;最佳实践:CI/CD集成:将EAS Build集成到GitHub Actions或其他CI/CD流程中版本管理:使用Git分支和EAS Update分支对应管理不同环境构建优化:合理配置构建配置,区分开发和生产环境监控和日志:定期查看构建日志,及时发现和解决问题权限管理:为团队成员分配适当的EAS权限限制和注意事项:iOS构建需要Apple开发者账号和付费开发者计划构建时间取决于项目大小和服务器负载免费账户有构建次数限制某些原生功能可能需要额外配置EAS大大简化了Expo应用的部署流程,使开发者能够更专注于应用开发本身。
阅读 0·2月21日 16:08

Expo CLI和Expo Go有什么区别?它们如何协同工作?

Expo CLI和Expo Go是Expo开发流程中的两个核心工具,它们各自承担不同的职责,协同工作以提供高效的开发体验。Expo CLI:Expo CLI是命令行工具,用于创建、构建和管理Expo项目。主要功能:项目初始化:通过npx create-expo-app命令快速创建新的Expo项目,支持TypeScript、JavaScript等多种模板。开发服务器:启动开发服务器,实时编译代码并提供热重载功能。构建配置:配置和管理项目的构建设置,包括应用图标、启动画面、权限配置等。打包发布:支持构建APK、IPA等安装包,或直接发布到Expo服务器。依赖管理:安装和更新Expo SDK版本及依赖包。常用命令:npx create-expo-app my-appnpx expo startnpx expo build:androidnpx expo build:iosExpo Go:Expo Go是一个移动应用,可在Android和iOS设备上安装,用于实时预览和测试Expo应用。主要功能:实时预览:通过扫描二维码或输入URL,在真实设备上查看应用效果。无需构建:开发过程中无需编译原生代码,大幅提升开发效率。跨设备测试:同时在多台设备上测试应用,验证不同屏幕尺寸和系统版本的兼容性。内置SDK:包含完整的Expo SDK,支持所有Expo组件和API。工作流程:使用Expo CLI创建项目并启动开发服务器在移动设备上安装Expo Go应用通过Expo Go连接到开发服务器实时查看代码修改效果限制:Expo Go不支持自定义原生代码,如果项目需要添加自定义原生模块,需要使用Expo Development Build或Eject流程。最佳实践:开发阶段优先使用Expo Go进行快速迭代测试阶段使用Development Build获得更接近生产环境的表现生产构建使用EAS Build生成优化的安装包这两个工具的结合使得Expo开发流程既快速又灵活,适合从原型到生产的完整开发周期。
阅读 0·2月21日 16:06

Expo如何支持Web平台?有哪些注意事项?

Expo支持Web平台,使开发者能够使用相同的代码库构建Web应用。这大大扩展了Expo的应用场景,实现了真正的跨平台开发。Expo for Web特点:单一代码库:使用相同的JavaScript/TypeScript代码响应式设计:自动适应不同屏幕尺寸Web API支持:访问浏览器原生APIPWA支持:可配置为渐进式Web应用快速开发:支持热重载和快速刷新配置Web支持:安装依赖:npx expo install react-dom react-native-web @expo/webpack-config配置app.json:{ "expo": { "web": { "bundler": "webpack", "output": "single", "favicon": "./assets/favicon.png" }, "experiments": { "typedRoutes": true } }}启动Web开发服务器:npx expo start --web平台特定代码:使用Platform模块处理平台差异: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:窗口API:// 获取窗口尺寸const width = window.innerWidth;const height = window.innerHeight;// 监听窗口大小变化window.addEventListener('resize', handleResize);本地存储:// 使用localStoragelocalStorage.setItem('key', 'value');const value = localStorage.getItem('key');导航API:// 使用浏览器历史window.history.pushState({}, '', '/new-route');window.history.back();样式适配:响应式样式:import { StyleSheet, Dimensions } from 'react-native';const styles = StyleSheet.create({ container: { width: Dimensions.get('window').width > 768 ? '80%' : '100%', padding: 16, },});CSS媒体查询:// 使用expo-linear-gradient等库import { LinearGradient } from 'expo-linear-gradient';<LinearGradient colors={['#4c669f', '#3b5998']} style={{ flex: 1 }}/>Web特定组件:HTML元素:// 在Web上使用HTML元素import { View, Text } from 'react-native';// 在Web上渲染为div和span<View style={{ padding: 16 }}> <Text>Hello Web</Text></View>Web特定库:// 使用react-web-specific库import { useMediaQuery } from 'react-responsive';const isDesktop = useMediaQuery({ minWidth: 992 });性能优化:代码分割:// 使用React.lazy进行代码分割const LazyComponent = React.lazy(() => import('./LazyComponent'));懒加载:// 懒加载图片import { Image } from 'react-native';<Image source={{ uri: 'https://example.com/image.jpg' }} loading="lazy"/>缓存策略:// 配置Service Worker进行缓存// 在public/sw.js中配置PWA配置:创建manifest.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" } ]}配置Service Worker:// public/sw.jsself.addEventListener('install', event => { event.waitUntil( caches.open('v1').then(cache => { return cache.addAll([ '/', '/index.html', '/static/js/main.js' ]); }) );});部署Web应用:构建生产版本:npx expo export:web部署到Vercel:# 安装Vercel CLInpm i -g vercel# 部署vercel部署到Netlify:# 安装Netlify CLInpm i -g netlify-cli# 部署netlify deploy --prod常见问题:样式差异:Web和移动端样式可能有所不同,需要测试和调整API兼容性:某些移动端API在Web上不可用,需要提供替代方案性能问题:Web版本可能比移动端慢,需要优化加载和渲染触摸事件:Web需要同时支持鼠标和触摸事件键盘导航:Web需要支持键盘导航和无障碍访问最佳实践:渐进增强:先实现核心功能,然后为Web添加特定优化响应式设计:确保应用在不同屏幕尺寸上都能良好显示性能监控:使用Web性能工具监控和优化加载速度SEO优化:添加meta标签和结构化数据测试覆盖:在多个浏览器和设备上测试Web版本Expo for Web使开发者能够用一套代码构建真正的跨平台应用,大大提高了开发效率和代码复用率。
阅读 0·2月21日 16:04

Expo有哪些常用的开发工具和调试技巧?

Expo提供了丰富的开发工具和调试功能,帮助开发者提高开发效率和代码质量。掌握这些工具对于Expo开发至关重要。核心开发工具:Expo CLIExpo CLI是主要的命令行工具,提供项目创建、开发服务器启动、构建等功能。常用命令:# 创建新项目npx create-expo-app my-app# 启动开发服务器npx expo start# 清除缓存npx expo start -c# 查看设备信息npx expo start --tunnel# 生成应用图标npx expo install expo-app-iconExpo Dev ToolsExpo Dev Tools是基于Web的开发工具界面,提供可视化的项目管理功能。功能:设备连接管理日志查看性能监控快速刷新控制网络请求监控React Native DebuggerReact Native Debugger是一个独立的调试工具,集成了React DevTools和Redux DevTools。使用方法:# 安装npm install -g react-native-debugger# 启动react-native-debugger调试技巧:Console调试使用console.log输出调试信息: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();React DevToolsReact DevTools提供组件树查看、props检查、状态监控等功能。使用方法:// 在开发环境中启用if (__DEV__) { const DevTools = require('react-devtools'); DevTools.connectToDevTools({ host: 'localhost', port: 8097, });}FlipperFlipper是Facebook开发的移动应用调试工具,支持网络请求、数据库、布局检查等。配置Flipper:// 在metro.config.js中const { getDefaultConfig } = require('expo/metro-config');const config = getDefaultConfig(__dirname);module.exports = config;ReactotronReactotron是一个强大的调试工具,支持Redux、API调用、日志等功能。配置Reactotron:import Reactotron from 'reactotron-react-native';if (__DEV__) { const tron = Reactotron .configure() .useReactNative() .connect(); console.tron = tron;}性能优化工具:性能监控使用React Native Performance API:import { Performance } from 'react-native';// 记录性能标记Performance.mark('component-start');// 组件渲染完成后Performance.mark('component-end');// 测量性能Performance.measure('component-render', 'component-start', 'component-end');内存分析使用Flipper或React Native Debugger查看内存使用情况:监控内存泄漏分析组件渲染性能优化图片和资源加载Bundle分析使用webpack-bundle-analyzer分析bundle大小:npm install --save-dev @expo/webpack-config webpack-bundle-analyzer错误处理:错误边界使用React错误边界捕获组件错误: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; }}全局错误处理监听全局错误事件:// 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);});测试工具:JestJest是Expo默认的测试框架:// 示例测试import { render } from '@testing-library/react-native';import MyComponent from './MyComponent';test('renders correctly', () => { const { getByText } = render(<MyComponent />); expect(getByText('Hello')).toBeTruthy();});DetoxDetox是端到端测试框架: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(); });});最佳实践:开发环境配置:使用不同的环境变量配置开发、测试和生产环境日志管理:在生产环境中禁用详细的日志输出错误追踪:集成Sentry或Bugsnag等错误追踪服务性能监控:定期监控应用性能指标自动化测试:建立完善的测试体系掌握这些调试工具和技巧,可以大大提高Expo开发的效率和质量。
阅读 0·2月21日 16:04

Expo提供了哪些常用的原生组件和API?如何使用它们?

Expo提供了丰富的原生组件和API,使开发者能够轻松访问移动设备的各种功能。这些组件和API经过精心设计,提供了统一的跨平台接口。核心原生组件:Camera(相机)import { Camera } from 'expo-camera';// 请求相机权限const { status } = await Camera.requestCameraPermissionsAsync();// 使用相机组件<Camera style={{ flex: 1 }} type={type} />Location(位置服务)import * as Location from 'expo-location';// 请求位置权限const { status } = await Location.requestForegroundPermissionsAsync();// 获取当前位置const location = await Location.getCurrentPositionAsync({});Notifications(推送通知)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 },});Audio(音频)import { Audio } from 'expo-av';// 播放音频const { sound } = await Audio.Sound.createAsync( { uri: 'https://example.com/audio.mp3' });await sound.playAsync();FileSystem(文件系统)import * as FileSystem from 'expo-file-system';// 读取文件const content = await FileSystem.readAsStringAsync(fileUri);// 写入文件await FileSystem.writeAsStringAsync(fileUri, 'Hello World');ImagePicker(图片选择器)import * as ImagePicker from 'expo-image-picker';// 选择图片const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], allowsEditing: true,});SecureStore(安全存储)import * as SecureStore from 'expo-secure-store';// 保存敏感数据await SecureStore.setItemAsync('token', 'user-token');// 读取敏感数据const token = await SecureStore.getItemAsync('token');Sensors(传感器)import { Accelerometer } from 'expo-sensors';// 监听加速度计Accelerometer.addListener(accelerometerData => { console.log(accelerometerData);});常用API:Constants(常量)import Constants from 'expo-constants';// 获取设备信息const deviceName = Constants.deviceName;const platform = Constants.platform;Haptics(触觉反馈)import * as Haptics from 'expo-haptics';// 触发触觉反馈Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);Linking(链接处理)import * as Linking from 'expo-linking';// 打开URLawait Linking.openURL('https://expo.dev');// 处理深度链接const url = await Linking.getInitialURL();ScreenOrientation(屏幕方向)import * as ScreenOrientation from 'expo-screen-orientation';// 锁定屏幕方向await ScreenOrientation.lockAsync( ScreenOrientation.OrientationLock.PORTRAIT);权限管理:Expo提供了统一的权限请求API:import * as Permissions from 'expo-permissions';// 请求权限const { status, granted } = await Permissions.askAsync( Permissions.CAMERA, Permissions.LOCATION);最佳实践:权限请求时机:在用户需要使用功能时再请求权限,提供清晰的说明错误处理:妥善处理权限被拒绝的情况,提供友好的用户提示性能优化:及时释放资源,如停止传感器监听、取消音频播放等平台差异:注意不同平台的API差异,使用条件渲染处理平台特定功能异步操作:所有原生API调用都是异步的,使用async/await处理这些原生组件和API大大简化了跨平台开发,使开发者能够专注于业务逻辑而不是原生实现细节。
阅读 0·2月21日 16:03

Expo应用的可访问性(Accessibility)如何实现?有哪些最佳实践?

Expo应用的可访问性(Accessibility)是确保所有用户,包括有视觉、听觉、运动或认知障碍的用户,都能有效使用应用的重要方面。Expo和React Native提供了丰富的可访问性API和属性。可访问性基础:accessibilityLabel为屏幕阅读器提供元素的描述。<Image source={{ uri: 'https://example.com/image.jpg' }} accessibilityLabel="用户头像" style={{ width: 50, height: 50 }}/><Button title="提交" accessibilityLabel="提交表单" onPress={handleSubmit}/>accessibilityHint提供有关元素行为的额外信息。<TouchableOpacity accessibilityLabel="查看详情" accessibilityHint="点击查看用户详细信息" onPress={handlePress}> <Text>查看</Text></TouchableOpacity>accessibilityRole指定元素的UI角色。<View accessibilityRole="button" accessibilityLabel="确认" onClick={handleConfirm}> <Text>确认</Text></View>常用可访问性角色:button:按钮link:链接header:标题text:文本image:图片search:搜索框adjustable:可调节控件accessibilityState描述元素的当前状态。<CheckBox accessibilityLabel="同意条款" accessibilityState={{ checked: isChecked, disabled: false, }} value={isChecked} onValueChange={setIsChecked}/>可访问性状态:disabled:禁用状态selected:选中状态checked:勾选状态busy:忙碌状态expanded:展开状态accessibilityValue描述元素的值。<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}/>可访问性操作:accessibilityActions定义元素支持的可访问性操作。<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>onAccessibilityEscape定义转义操作。<Modal visible={isVisible} onAccessibilityEscape={() => setIsVisible(false)} accessibilityViewIsModal={true}> <View> <Text>模态框内容</Text> <Button title="关闭" onPress={() => setIsVisible(false)} /> </View></Modal>可访问性属性:accessible标记元素为可访问的。<View accessible={true}> <Text>这个视图可以被屏幕阅读器访问</Text></View>accessibilityElementsHidden隐藏子元素的可访问性。<View accessible={true} accessibilityLabel="容器" accessibilityElementsHidden={isHidden}> <Text>子元素1</Text> <Text>子元素2</Text></View>accessibilityIgnoresInvertColors忽略颜色反转设置。<Image source={{ uri: 'https://example.com/chart.jpg' }} accessibilityIgnoresInvertColors={true} style={{ width: 200, height: 200 }}/>焦点管理:focusable使元素可聚焦。<TextInput focusable={true} accessibilityLabel="用户名输入框" placeholder="请输入用户名"/>accessibilityLiveRegion标记动态内容区域。<Text accessibilityLiveRegion="polite" accessibilityLabel="状态信息"> {statusMessage}</Text>可访问性事件: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 };}语义化组件:使用语义化HTML标签(Web)// 在Web平台上使用语义化标签if (Platform.OS === 'web') { return ( <nav accessibilityRole="navigation"> <ul> <li><a href="/home">首页</a></li> <li><a href="/about">关于</a></li> </ul> </nav> );}使用正确的可访问性角色// 按钮使用button角色<TouchableOpacity accessibilityRole="button" accessibilityLabel="提交" onPress={handleSubmit}> <Text>提交</Text></TouchableOpacity>// 链接使用link角色<TouchableOpacity accessibilityRole="link" accessibilityLabel="查看详情" onPress={handlePress}> <Text>查看详情</Text></TouchableOpacity>最佳实践:提供清晰的标签// 好的实践<Button title="提交表单" accessibilityLabel="提交用户注册表单" onPress={handleSubmit}/>// 避免重复<Button title="提交" accessibilityLabel="提交" // 与title重复 onPress={handleSubmit}/>使用有意义的提示<TouchableOpacity accessibilityLabel="删除项目" accessibilityHint="此操作无法撤销,请谨慎操作" onPress={handleDelete}> <Text>删除</Text></TouchableOpacity>支持键盘导航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> );}支持屏幕阅读器function ScreenReaderSupport() { const isScreenReaderEnabled = useAccessibilityInfo(); return ( <View> {isScreenReaderEnabled ? ( <Text>屏幕阅读器已启用</Text> ) : ( <Text>屏幕阅读器未启用</Text> )} </View> );}测试可访问性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); } );}可访问性工具:AccessibilityInfo APIimport { 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);});可访问性检查工具iOS:VoiceOverAndroid:TalkBackWeb:屏幕阅读器(NVDA、JAWS等)常见可访问性问题:缺少可访问性标签为所有交互元素添加accessibilityLabel为图片提供描述性标签焦点管理不当确保键盘导航顺序合理提供清晰的焦点指示器颜色对比度不足确保文本和背景有足够的对比度支持高对比度模式动态内容未通知使用accessibilityLiveRegion标记动态内容及时通知屏幕阅读器内容变化通过实施这些可访问性实践,可以确保Expo应用对所有用户都是友好和可用的。
阅读 0·2月21日 16:03

什么是Expo Router?它如何实现文件系统路由?

Expo Router是Expo官方提供的路由解决方案,基于文件系统路由,专为Expo应用设计。它简化了导航管理,提供了类型安全的路由和深度链接支持。核心特性:文件系统路由基于文件和文件夹结构自动生成路由支持动态路由和嵌套路由类似Next.js的路由体验类型安全自动生成TypeScript类型编译时路由检查智能代码补全深度链接原生深度链接支持Web URL兼容自动处理链接参数安装和配置:# 安装Expo Routernpx 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布局路由类型:静态路由// app/index.tsxexport default function HomeScreen() { return <Text>Home</Text>;}动态路由// app/user/[id].tsximport { useLocalSearchParams } from 'expo-router';export default function UserScreen() { const { id } = useLocalSearchParams<{ id: string }>(); return <Text>User: {id}</Text>;}嵌套路由// app/(tabs)/_layout.tsximport { 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> );}分组路由// (tabs)和(modal)是路由组,不影响URL// app/(tabs)/home.tsx -> /home// app/(modal)/settings.tsx -> /settings导航API: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:import { Link, useLocalSearchParams } from 'expo-router';// 使用Link组件<Link href="/user/123"> <Text>Go to User</Text></Link>// 使用useLocalSearchParams获取参数const { id } = useLocalSearchParams<{ id: string }>();深度链接配置:// app/_layout.tsximport { 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 />;}最佳实践:路由组织:合理使用路由组和嵌套布局,保持结构清晰类型安全:启用typedRoutes实验性功能,获得完整的类型支持性能优化:使用懒加载减少初始包大小错误处理:实现404页面和错误边界测试覆盖:为路由逻辑编写单元测试与React Navigation的对比:Expo Router基于React Navigation构建,提供了更高级的抽象:更简单的配置自动类型生成文件系统路由更好的深度链接支持Expo Router是构建Expo应用导航的理想选择,特别适合需要类型安全和深度链接的项目。
阅读 0·2月21日 16:03

Expo中如何实现动画效果?有哪些常用的动画库?

Expo动画是提升用户体验的重要手段。Expo支持多种动画库和API,从简单的过渡效果到复杂的交互动画都有完善的解决方案。动画库选择:React Native Animated APIReact Native内置的动画API,适合大多数动画需求。基础动画: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> );}插值动画: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> );}并行和序列动画: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> );}React Native Reanimated更强大的动画库,支持手势和复杂动画。安装:npx expo install react-native-reanimated基础动画: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> );}手势动画: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> );}Lottie使用Adobe After Effects创建的复杂动画。安装:npx expo install lottie-react-native使用Lottie动画:import LottieView from 'lottie-react-native';function LottieComponent() { return ( <LottieView source={require('./assets/animation.json')} autoPlay loop style={{ width: 200, height: 200 }} /> );}控制Lottie动画: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> );}常用动画模式:淡入淡出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> );}滑动动画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> );}缩放动画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> );}旋转动画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> );}性能优化:使用原生驱动// 使用useNativeDriver提高性能Animated.timing(value, { toValue: 1, duration: 500, useNativeDriver: true, // 在UI线程运行}).start();避免在动画中使用复杂布局// 避免在动画组件中使用flex布局const style = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], // 避免使用flex相关的属性}));使用shouldComponentUpdate优化const AnimatedComponent = React.memo(({ value }) => { const animatedStyle = useAnimatedStyle(() => ({ opacity: value.value, })); return <Animated.View style={animatedStyle} />;});最佳实践:选择合适的动画库简单动画:React Native Animated复杂动画和手势:React Native Reanimated设计师创建的动画:Lottie性能优先使用原生驱动避免在动画中使用复杂布局使用useMemo和useCallback优化用户体验提供流畅的过渡效果避免过度动画考虑性能较差的设备可访问性为动画提供替代方案尊重用户的减少动画偏好提供动画控制选项通过合理使用动画,可以显著提升Expo应用的用户体验和交互质量。
阅读 0·2月21日 16:03

什么是Expo框架?它有哪些核心优势?

Expo是一个基于React Native的开源框架,它简化了跨平台移动应用的开发流程。Expo提供了完整的开发工具链、预构建的原生组件和云服务,使开发者能够快速构建、测试和部署Android、iOS和Web应用。核心优势:快速开发:Expo提供了Expo CLI和Expo Go应用,开发者可以立即在真实设备上预览应用,无需配置复杂的原生开发环境。跨平台支持:一次编写代码,即可在Android、iOS和Web平台运行,大幅减少开发成本和维护工作量。丰富的组件库:Expo提供了超过40个预构建的原生组件,如相机、位置服务、推送通知等,无需编写原生代码即可使用。OTA更新:支持通过Expo Over-the-Air更新机制,无需重新提交应用商店即可推送更新。云服务集成:提供Expo Application Services (EAS),包括构建、提交、更新等服务。工作原理:Expo在React Native之上构建了一个抽象层,提供了统一的API来访问原生功能。开发者使用JavaScript/TypeScript编写代码,Expo SDK处理与原生平台的交互。适用场景:快速原型开发中小型跨平台应用需要快速迭代的项目团队缺乏原生开发经验限制:对于需要深度原生功能的应用可能不够灵活应用包体积相对较大某些高级功能需要使用Expo Development Build或EjectExpo持续更新,目前最新版本支持React Native的最新特性,并不断扩展功能和性能优化。
阅读 0·2月21日 16:02

Expo的OTA更新是如何工作的?如何使用EAS Update?

Expo的OTA (Over-the-Air) 更新功能允许开发者在不重新提交应用商店的情况下推送应用更新。这是一个强大的功能,可以显著加快迭代速度。OTA更新原理:Expo OTA通过将JavaScript bundle和资源文件上传到Expo服务器,然后在应用启动时检查并下载更新来实现。工作流程:开发者使用eas update命令上传更新Expo服务器存储更新并分配唯一版本号应用启动时检查服务器是否有新版本如果有新版本,下载并应用更新用户下次打开应用时看到更新内容EAS Update使用方法:安装EAS CLI:npm install -g eas-cli配置项目:eas build:configure创建更新:# 创建并发布更新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"查看更新历史:# 查看所有更新eas update:list# 查看特定分支的更新eas update:list --branch production回滚更新:# 回滚到上一个版本eas update:rollback --branch production# 回滚到特定版本eas update:rollback --branch production --target-message "Previous stable version"配置app.json:{ "expo": { "updates": { "url": "https://u.expo.dev/your-project-id" }, "runtimeVersion": { "policy": "appVersion" } }}运行时版本策略:Expo支持多种运行时版本策略:appVersion策略(推荐):{ "runtimeVersion": { "policy": "appVersion" }}使用应用版本号作为运行时版本简单直接,适合大多数场景nativeVersion策略:{ "runtimeVersion": { "policy": "nativeVersion" }}使用原生代码版本更精确的控制customVersion策略:{ "runtimeVersion": { "policy": "customVersion", "customVersion": "1.0.0" }}自定义版本号最大的灵活性更新分组:可以创建不同的更新分支来管理不同环境的更新:# 生产环境更新eas update --branch production# 预览环境更新eas update --branch preview# 开发环境更新eas update --branch development客户端配置: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); }});限制和注意事项:只能更新JavaScript和资源:不能更新原生代码应用商店审核:某些应用商店对OTA更新有政策限制版本兼容性:更新必须与当前运行时版本兼容网络依赖:用户需要网络连接才能接收更新回滚机制:需要实现回滚策略以应对问题更新最佳实践:渐进式发布:先在小范围用户中测试更新版本管理:保持清晰的版本号和更新日志错误监控:监控更新后的错误率用户通知:在更新前通知用户回滚准备:随时准备回滚到稳定版本测试覆盖:在发布前充分测试更新常见问题:更新不生效:检查运行时版本是否匹配更新失败:查看服务器日志和客户端错误性能影响:监控更新下载对应用性能的影响安全考虑:确保更新传输使用HTTPS加密Expo OTA更新是快速迭代应用的重要工具,合理使用可以大大提升开发效率和用户体验。
阅读 0·2月21日 16:02