Loading...
Loading...
Compare original and translation side by side
react-nativeexporeact-nativeexposrc/
├── components/ # Shared components
│ ├── ui/ # Base UI components
│ └── common/ # Business components
├── screens/ # Screen components
├── navigation/ # Navigation config
├── hooks/ # Custom hooks
├── services/ # API services
├── stores/ # State management
├── utils/ # Utilities
├── constants/ # App constants
├── types/ # TypeScript types
└── assets/ # Images, fontssrc/
├── components/ # 通用组件
│ ├── ui/ # 基础UI组件
│ └── common/ # 业务组件
├── screens/ # 页面组件
├── navigation/ # 导航配置
├── hooks/ # 自定义hooks
├── services/ # API服务
├── stores/ # 状态管理
├── utils/ # 工具函数
├── constants/ # 应用常量
├── types/ # TypeScript类型定义
└── assets/ # 图片、字体等资源// ✅ GOOD - Detect and use project's styling approach
// Check package.json for: nativewind, emotion, styled-components, or use StyleSheet
// NativeWind (Tailwind)
import { styled } from 'nativewind';
const StyledView = styled(View);
<StyledView className="flex-1 bg-white p-4" />
// StyleSheet (default)
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white', padding: 16 },
});
<View style={styles.container} />
// Emotion
import styled from '@emotion/native';
const Container = styled.View`flex: 1; background-color: white;`;// ✅ 推荐 - 检测并适配项目的样式方案
// 检查package.json中是否包含:nativewind、emotion、styled-components,或使用StyleSheet
// NativeWind(Tailwind风格)
import { styled } from 'nativewind';
const StyledView = styled(View);
<StyledView className="flex-1 bg-white p-4" />
// StyleSheet(默认方案)
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white', padding: 16 },
});
<View style={styles.container} />
// Emotion
import styled from '@emotion/native';
const Container = styled.View`flex: 1; background-color: white;`;// ✅ GOOD - Memoized list item
const ListItem = React.memo(function ListItem({ item, onPress }: Props) {
const handlePress = useCallback(() => {
onPress(item.id);
}, [item.id, onPress]);
return (
<Pressable onPress={handlePress}>
<Text>{item.title}</Text>
</Pressable>
);
});
// ✅ GOOD - Pressable over TouchableOpacity
<Pressable
onPress={handlePress}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
android_ripple={{ color: 'rgba(0,0,0,0.1)' }}
>
<Text>Press Me</Text>
</Pressable>// ✅ 推荐 - 记忆化列表项组件
const ListItem = React.memo(function ListItem({ item, onPress }: Props) {
const handlePress = useCallback(() => {
onPress(item.id);
}, [item.id, onPress]);
return (
<Pressable onPress={handlePress}>
<Text>{item.title}</Text>
</Pressable>
);
});
// ✅ 推荐 - 使用Pressable替代TouchableOpacity
<Pressable
onPress={handlePress}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
android_ripple={{ color: 'rgba(0,0,0,0.1)' }}
>
<Text>Press Me</Text>
</Pressable>// ✅ GOOD - Optimized FlatList
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={keyExtractor}
// Performance props
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={10}
getItemLayout={getItemLayout} // If fixed height
// Memoized functions
ListEmptyComponent={EmptyComponent}
ListHeaderComponent={HeaderComponent}
ListFooterComponent={FooterComponent}
// Prevent re-renders
extraData={selectedId}
/>
// ✅ GOOD - Memoized renderItem
const renderItem = useCallback(({ item }: { item: Item }) => (
<ListItem item={item} onPress={handlePress} />
), [handlePress]);
// ✅ GOOD - Stable keyExtractor
const keyExtractor = useCallback((item: Item) => item.id, []);
// ✅ GOOD - getItemLayout for fixed height items
const getItemLayout = useCallback((
_data: Item[] | null | undefined,
index: number
) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}), []);// ✅ 推荐 - 优化后的FlatList
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={keyExtractor}
// 性能相关属性
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={10}
getItemLayout={getItemLayout} // 固定高度时使用
// 记忆化函数
ListEmptyComponent={EmptyComponent}
ListHeaderComponent={HeaderComponent}
ListFooterComponent={FooterComponent}
// 防止不必要重渲染
extraData={selectedId}
/>
// ✅ 推荐 - 记忆化renderItem函数
const renderItem = useCallback(({ item }: { item: Item }) => (
<ListItem item={item} onPress={handlePress} />
), [handlePress]);
// ✅ 推荐 - 稳定的keyExtractor函数
const keyExtractor = useCallback((item: Item) => item.id, []);
// ✅ 推荐 - 固定高度列表使用getItemLayout
const getItemLayout = useCallback((
_data: Item[] | null | undefined,
index: number
) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}), []);// ✅ BETTER - Use FlashList for large lists
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={renderItem}
estimatedItemSize={ITEM_HEIGHT}
keyExtractor={keyExtractor}
/>// ✅ 更优 - 大数据列表使用FlashList
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={renderItem}
estimatedItemSize={ITEM_HEIGHT}
keyExtractor={keyExtractor}
/>// ✅ GOOD - Define navigation types
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: { section?: string };
};
type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>;
// ✅ GOOD - Type-safe navigation hook
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
function MyComponent() {
const navigation = useNavigation<NavigationProp>();
const goToProfile = (userId: string) => {
navigation.navigate('Profile', { userId });
};
}// ✅ 推荐 - 定义导航类型
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: { section?: string };
};
type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>;
// ✅ 推荐 - 类型安全的导航Hook
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
function MyComponent() {
const navigation = useNavigation<NavigationProp>();
const goToProfile = (userId: string) => {
navigation.navigate('Profile', { userId });
};
}// ✅ GOOD - Configure deep linking
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: '',
Profile: 'user/:userId',
Settings: 'settings',
},
},
};
<NavigationContainer linking={linking}>
{/* ... */}
</NavigationContainer>// ✅ 推荐 - 配置深度链接
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: '',
Profile: 'user/:userId',
Settings: 'settings',
},
},
};
<NavigationContainer linking={linking}>
{/* ... */}
</NavigationContainer>import { Platform, StyleSheet } from 'react-native';
// ✅ GOOD - Platform.select
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
android: {
elevation: 5,
},
default: {},
}),
});
// ✅ GOOD - Platform-specific files
// Button.ios.tsx
// Button.android.tsx
// Button.tsx (fallback)
import Button from './Button'; // Auto-selects platformimport { Platform, StyleSheet } from 'react-native';
// ✅ 推荐 - 使用Platform.select
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
android: {
elevation: 5,
},
default: {},
}),
});
// ✅ 推荐 - 平台特定文件
// Button.ios.tsx
// Button.android.tsx
// Button.tsx( fallback )
import Button from './Button'; // 自动适配平台import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
// ✅ GOOD - SafeAreaView for screens
function Screen() {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
<Content />
</SafeAreaView>
);
}
// ✅ GOOD - useSafeAreaInsets for custom handling
function CustomHeader() {
const insets = useSafeAreaInsets();
return (
<View style={{ paddingTop: insets.top }}>
<HeaderContent />
</View>
);
}import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
// ✅ 推荐 - 页面使用SafeAreaView
function Screen() {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
<Content />
</SafeAreaView>
);
}
// ✅ 推荐 - 自定义场景使用useSafeAreaInsets
function CustomHeader() {
const insets = useSafeAreaInsets();
return (
<View style={{ paddingTop: insets.top }}>
<HeaderContent />
</View>
);
}import FastImage from 'react-native-fast-image';
// ✅ GOOD - Use FastImage for network images
<FastImage
source={{
uri: imageUrl,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable,
}}
style={styles.image}
resizeMode={FastImage.resizeMode.cover}
/>
// ✅ GOOD - Preload images
FastImage.preload([
{ uri: 'https://example.com/image1.jpg' },
{ uri: 'https://example.com/image2.jpg' },
]);
// ✅ GOOD - Local images with require
<Image source={require('./assets/logo.png')} />import FastImage from 'react-native-fast-image';
// ✅ 推荐 - 网络图片使用FastImage
<FastImage
source={{
uri: imageUrl,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable,
}}
style={styles.image}
resizeMode={FastImage.resizeMode.cover}
/>
// ✅ 推荐 - 预加载图片
FastImage.preload([
{ uri: 'https://example.com/image1.jpg' },
{ uri: 'https://example.com/image2.jpg' },
]);
// ✅ 推荐 - 本地图片使用require
<Image source={require('./assets/logo.png')} />import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
runOnJS,
} from 'react-native-reanimated';
// ✅ GOOD - Worklet-based animations
function AnimatedCard() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const handlePressIn = () => {
scale.value = withSpring(0.95);
};
const handlePressOut = () => {
scale.value = withSpring(1);
};
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Animated.View style={[styles.card, animatedStyle]}>
<Content />
</Animated.View>
</Pressable>
);
}import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
runOnJS,
} from 'react-native-reanimated';
// ✅ 推荐 - 基于Worklet的动画
function AnimatedCard() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const handlePressIn = () => {
scale.value = withSpring(0.95);
};
const handlePressOut = () => {
scale.value = withSpring(1);
};
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Animated.View style={[styles.card, animatedStyle]}>
<Content />
</Animated.View>
</Pressable>
);
}import { Gesture, GestureDetector } from 'react-native-gesture-handler';
// ✅ GOOD - Gesture-based interactions
function SwipeableCard() {
const translateX = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
})
.onEnd(() => {
translateX.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={gesture}>
<Animated.View style={animatedStyle}>
<Card />
</Animated.View>
</GestureDetector>
);
}import { Gesture, GestureDetector } from 'react-native-gesture-handler';
// ✅ 推荐 - 基于手势的交互
function SwipeableCard() {
const translateX = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
})
.onEnd(() => {
translateX.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={gesture}>
<Animated.View style={animatedStyle}>
<Card />
</Animated.View>
</GestureDetector>
);
}import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
// ✅ GOOD - AsyncStorage for non-sensitive data
async function saveSettings(settings: Settings) {
await AsyncStorage.setItem('settings', JSON.stringify(settings));
}
// ✅ GOOD - SecureStore for sensitive data
async function saveToken(token: string) {
await SecureStore.setItemAsync('authToken', token);
}
// ✅ GOOD - MMKV for performance-critical storage
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
storage.set('user.name', 'John');
const name = storage.getString('user.name');import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
// ✅ 推荐 - 非敏感数据使用AsyncStorage
async function saveSettings(settings: Settings) {
await AsyncStorage.setItem('settings', JSON.stringify(settings));
}
// ✅ 推荐 - 敏感数据使用SecureStore
async function saveToken(token: string) {
await SecureStore.setItemAsync('authToken', token);
}
// ✅ 推荐 - 性能敏感场景使用MMKV
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
storage.set('user.name', 'John');
const name = storage.getString('user.name');import { render, fireEvent, waitFor } from '@testing-library/react-native';
// ✅ GOOD - Component testing
describe('LoginButton', () => {
it('calls onPress when pressed', () => {
const onPress = jest.fn();
const { getByText } = render(<LoginButton onPress={onPress} />);
fireEvent.press(getByText('Login'));
expect(onPress).toHaveBeenCalled();
});
});
// ✅ GOOD - Async testing
it('shows loading then content', async () => {
const { getByTestId, queryByTestId } = render(<DataScreen />);
expect(getByTestId('loading')).toBeTruthy();
await waitFor(() => {
expect(queryByTestId('loading')).toBeNull();
expect(getByTestId('content')).toBeTruthy();
});
});import { render, fireEvent, waitFor } from '@testing-library/react-native';
// ✅ 推荐 - 组件测试
describe('LoginButton', () => {
it('点击时触发onPress', () => {
const onPress = jest.fn();
const { getByText } = render(<LoginButton onPress={onPress} />);
fireEvent.press(getByText('Login'));
expect(onPress).toHaveBeenCalled();
});
});
// ✅ 推荐 - 异步测试
it('先显示加载状态再显示内容', async () => {
const { getByTestId, queryByTestId } = render(<DataScreen />);
expect(getByTestId('loading')).toBeTruthy();
await waitFor(() => {
expect(queryByTestId('loading')).toBeNull();
expect(getByTestId('content')).toBeTruthy();
});
});checklist[12]{area,best_practice}:
Lists,Use FlashList or optimized FlatList
Images,Use FastImage for network images
Navigation,Type-safe with RootStackParamList
Styling,Detect project approach (NativeWind/StyleSheet)
Platform,Use Platform.select for differences
Safe area,Use SafeAreaView or useSafeAreaInsets
Animations,Use Reanimated for 60fps
Gestures,Use Gesture Handler
Storage,SecureStore for tokens AsyncStorage for prefs
Performance,Memoize components and callbacks
Testing,React Native Testing Library
Pressable,Use over TouchableOpacitychecklist[12]{area,best_practice}:
Lists,Use FlashList or optimized FlatList
Images,Use FastImage for network images
Navigation,Type-safe with RootStackParamList
Styling,Detect project approach (NativeWind/StyleSheet)
Platform,Use Platform.select for differences
Safe area,Use SafeAreaView or useSafeAreaInsets
Animations,Use Reanimated for 60fps
Gestures,Use Gesture Handler
Storage,SecureStore for tokens AsyncStorage for prefs
Performance,Memoize components and callbacks
Testing,React Native Testing Library
Pressable,Use over TouchableOpacity