Expo
Expo是一个面向Android、iOS和网页应用的开源框架。Expo汇集了移动和网络的精华,为构建和扩展应用程序提供了许多重要的功能。Expo npm包为React Native应用程序提供了一套令人难以置信的功能。

查看更多相关内容
什么是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