Mobile development with React Native
Combining React Native with TypeScript
React Native, as a cross-platform mobile app development framework, when combined with TypeScript's static type-checking capabilities, can significantly improve development efficiency and code quality. TypeScript brings type safety, better code hints, and refactoring capabilities to React Native projects, making it particularly suitable for medium to large-scale applications.
// A simple React Native component example
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
interface Props {
title: string;
count: number;
}
const Counter: React.FC<Props> = ({ title, count }) => {
return (
<View style={styles.container}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.count}>{count}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
backgroundColor: '#f5f5f5'
},
title: {
fontSize: 18,
fontWeight: 'bold'
},
count: {
fontSize: 24,
color: '#333'
}
});
export default Counter;
Application of Type Definitions in React Native
When using TypeScript with React Native, the first step is to define the types for component props and state. This helps catch potential type errors during development while providing better code auto-completion.
// Defining complex props types
type User = {
id: string;
name: string;
avatar: string;
online: boolean;
};
interface UserCardProps {
user: User;
onPress?: (userId: string) => void;
style?: ViewStyle;
}
const UserCard: React.FC<UserCardProps> = ({ user, onPress, style }) => {
// Component implementation
};
For navigation parameter type definitions, you can create a global type file:
// types/navigation.ts
export type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: { initialTab?: 'general' | 'notifications' };
};
// Usage example
import { StackScreenProps } from '@react-navigation/stack';
type ProfileScreenProps = StackScreenProps<RootStackParamList, 'Profile'>;
const ProfileScreen = ({ route }: ProfileScreenProps) => {
const { userId } = route.params; // Automatically infers that userId is of type string
};
Type-Safe Style Handling
React Native's StyleSheet.create
method already provides basic type checking, but we can further enhance it:
// Defining theme types
interface Theme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
};
spacing: {
small: number;
medium: number;
large: number;
};
}
// Creating typed styles
const makeStyles = (theme: Theme) =>
StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
padding: theme.spacing.medium,
},
button: {
backgroundColor: theme.colors.primary,
paddingVertical: theme.spacing.small,
paddingHorizontal: theme.spacing.medium,
borderRadius: 4,
},
});
Handling API Response Data
When interacting with backend APIs, TypeScript ensures the correctness of data structures:
// Defining API response types
interface ApiResponse<T> {
data: T;
status: number;
message?: string;
}
interface Post {
id: number;
title: string;
content: string;
createdAt: string;
author: {
id: number;
name: string;
};
}
// Function to fetch post list
async function fetchPosts(): Promise<ApiResponse<Post[]>> {
const response = await fetch('https://api.example.com/posts');
return response.json();
}
// Usage example
const loadPosts = async () => {
try {
const { data: posts } = await fetchPosts();
// posts now has full type hints
posts.forEach(post => console.log(post.title));
} catch (error) {
console.error('Failed to load posts:', error);
}
};
Type Handling in Higher-Order Components and Hooks
When creating custom hooks, TypeScript ensures type safety for inputs and outputs:
// Custom Hook example: Using device information
import { useState, useEffect } from 'react';
import { Platform, Dimensions } from 'react-native';
interface DeviceInfo {
platform: 'ios' | 'android' | 'windows' | 'macos' | 'web';
screenWidth: number;
screenHeight: number;
isTablet: boolean;
}
function useDeviceInfo(): DeviceInfo {
const [deviceInfo, setDeviceInfo] = useState<DeviceInfo>({
platform: Platform.OS,
screenWidth: Dimensions.get('window').width,
screenHeight: Dimensions.get('window').height,
isTablet: Dimensions.get('window').width >= 600,
});
useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => {
setDeviceInfo(prev => ({
...prev,
screenWidth: window.width,
screenHeight: window.height,
isTablet: window.width >= 600,
}));
});
return () => subscription.remove();
}, []);
return deviceInfo;
}
Higher-order components also need to handle types correctly:
// Higher-order component example: withLoading
import React, { ComponentType } from 'react';
import { ActivityIndicator, View, StyleSheet } from 'react-native';
interface WithLoadingProps {
loading: boolean;
}
function withLoading<P extends object>(
WrappedComponent: ComponentType<P>
): React.FC<P & WithLoadingProps> {
return ({ loading, ...props }) => {
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" />
</View>
);
}
return <WrappedComponent {...(props as P)} />;
};
}
const styles = StyleSheet.create({
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
// Usage example
interface UserListProps {
users: User[];
}
const UserList: React.FC<UserListProps> = ({ users }) => {
// Render user list
};
const UserListWithLoading = withLoading(UserList);
Type Safety in State Management
When using state management libraries like Redux or MobX, TypeScript provides strong type support:
// Redux example
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface AuthState {
token: string | null;
user: User | null;
loading: boolean;
error: string | null;
}
const initialState: AuthState = {
token: null,
user: null,
loading: false,
error: null,
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
loginStart(state) {
state.loading = true;
state.error = null;
},
loginSuccess(state, action: PayloadAction<{ token: string; user: User }>) {
state.token = action.payload.token;
state.user = action.payload.user;
state.loading = false;
},
loginFailure(state, action: PayloadAction<string>) {
state.loading = false;
state.error = action.payload;
},
logout(state) {
state.token = null;
state.user = null;
},
},
});
// Usage in components
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../store';
const LoginButton = () => {
const dispatch = useDispatch();
const loading = useSelector((state: RootState) => state.auth.loading);
const handleLogin = async () => {
dispatch(authSlice.actions.loginStart());
try {
const response = await loginApi(username, password);
dispatch(authSlice.actions.loginSuccess(response));
} catch (error) {
dispatch(authSlice.actions.loginFailure(error.message));
}
};
return (
<Button
title="Login"
onPress={handleLogin}
loading={loading}
/>
);
};
Type Hints for Performance Optimization
When optimizing React Native performance, TypeScript can help identify potential issues:
// Optimizing components with React.memo
interface ItemProps {
id: string;
title: string;
onPress: (id: string) => void;
isSelected: boolean;
}
const areEqual = (prevProps: ItemProps, nextProps: ItemProps) => {
return (
prevProps.id === nextProps.id &&
prevProps.title === nextProps.title &&
prevProps.isSelected === nextProps.isSelected
);
};
const MemoizedItem = React.memo(({ id, title, onPress, isSelected }: ItemProps) => {
// Component implementation
}, areEqual);
// Using useCallback to avoid unnecessary re-renders
const ParentComponent = () => {
const [selectedId, setSelectedId] = useState<string | null>(null);
// Using useCallback to ensure stable function reference
const handleItemPress = useCallback((id: string) => {
setSelectedId(id === selectedId ? null : id);
}, [selectedId]);
return (
<FlatList
data={items}
renderItem={({ item }) => (
<MemoizedItem
id={item.id}
title={item.title}
onPress={handleItemPress}
isSelected={item.id === selectedId}
/>
)}
keyExtractor={item => item.id}
/>
);
};
Type Application in Testing
When writing tests, TypeScript ensures the correctness of test data:
// Testing component example
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import Button from '../Button';
describe('Button Component', () => {
it('should call onPress when clicked', () => {
const mockOnPress = jest.fn();
const { getByText } = render(
<Button title="Click me" onPress={mockOnPress} />
);
fireEvent.press(getByText('Click me'));
expect(mockOnPress).toHaveBeenCalled();
});
it('should show loading indicator when loading', () => {
const { getByTestId } = render(
<Button title="Submit" loading={true} />
);
expect(getByTestId('loading-indicator')).toBeTruthy();
});
});
// Testing utility functions
function formatUserName(user: User): string {
return `${user.name} (${user.id.slice(0, 4)})`;
}
describe('formatUserName', () => {
it('should format user name correctly', () => {
const testUser: User = {
id: 'user-123456',
name: 'John Doe',
avatar: '',
online: true,
};
expect(formatUserName(testUser)).toBe('John Doe (user)');
});
});
Type Extensions for Third-Party Libraries
Many popular React Native libraries provide TypeScript support, but sometimes type definitions need to be extended:
// Extending react-native-vector-icons types
import Icon from 'react-native-vector-icons/MaterialIcons';
declare module 'react-native-vector-icons/MaterialIcons' {
export interface IconProps {
customColor?: string;
customSize?: number;
}
}
// Extending react-navigation types
import 'react-navigation';
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
// Using custom icon component
const CustomIcon: React.FC<IconProps> = ({ name, customColor, customSize }) => {
return (
<Icon
name={name}
color={customColor || '#333'}
size={customSize || 24}
/>
);
};
Type Handling for Complex Forms
When dealing with complex forms, TypeScript ensures the correct structure of form data:
// Form type definitions
interface FormValues {
personalInfo: {
firstName: string;
lastName: string;
email: string;
phone?: string;
};
address: {
street: string;
city: string;
postalCode: string;
country: string;
};
preferences: {
newsletter: boolean;
notifications: boolean;
theme: 'light' | 'dark' | 'system';
};
}
// Using Formik and Yup for form validation
import { Formik } from 'formik';
import * as yup from 'yup';
const validationSchema = yup.object().shape({
personalInfo: yup.object().shape({
firstName: yup.string().required('Required'),
lastName: yup.string().required('Required'),
email: yup.string().email('Invalid email').required('Required'),
phone: yup.string().matches(/^[0-9]+$/, 'Must be only digits'),
}),
address: yup.object().shape({
street: yup.string().required('Required'),
city: yup.string().required('Required'),
postalCode: yup.string().required('Required'),
country: yup.string().required('Required'),
}),
});
const UserForm = () => {
const initialValues: FormValues = {
personalInfo: {
firstName: '',
lastName: '',
email: '',
phone: '',
},
address: {
street: '',
city: '',
postalCode: '',
country: '',
},
preferences: {
newsletter: false,
notifications: true,
theme: 'system',
},
};
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values) => console.log(values)}
>
{({ handleChange, handleBlur, handleSubmit, values, errors }) => (
// Form rendering logic
)}
</Formik>
);
};
Type Safety in Animation Handling
React Native animation APIs can also benefit from TypeScript:
import { Animated, Easing } from 'react-native';
interface AnimatedBoxProps {
color: string;
startValue?: number;
}
const AnimatedBox: React.FC<AnimatedBoxProps> = ({ color, startValue = 0 }) => {
const translateY = React.useRef(new Animated.Value(startValue)).current;
const animate = () => {
Animated.sequence([
Animated.timing(translateY, {
toValue: 100,
duration: 500,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true,
}),
Animated.timing(translateY, {
toValue: 0,
duration: 500,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true,
}),
]).start();
};
return (
<Animated.View
style={{
width: 100,
height: 100,
backgroundColor: color,
transform: [{ translateY }],
}}
/>
);
};
Type Handling for Platform-Specific Code
When dealing with platform-specific code, TypeScript helps ensure correct platform branching:
// Platform-specific component
import { Platform, StyleSheet } from 'react-native';
interface PlatformButtonProps {
title: string;
onPress: () => void;
style?: ViewStyle;
}
const PlatformButton: React.FC<PlatformButtonProps> = ({ title, onPress, style }) => {
return Platform.select({
ios: (
<TouchableOpacity onPress={onPress} style={[styles.iosButton, style]}>
<Text style={styles.iosButtonText}>{title}</Text>
</TouchableOpacity>
),
android: (
<TouchableNativeFeedback onPress={onPress}>
<View style={[styles.androidButton, style]}>
<Text style={styles.androidButtonText}>{title}</Text>
</View>
</TouchableNativeFeedback>
),
default: (
<TouchableOpacity onPress={onPress} style={[styles.defaultButton, style]}>
<Text style={styles.defaultButtonText}>{title}</Text>
</TouchableOpacity>
),
});
};
const styles = StyleSheet.create({
iosButton: {
padding: 12,
backgroundColor: '#007AFF',
borderRadius: 8,
},
iosButtonText: {
color: 'white',
textAlign: 'center',
},
androidButton: {
padding: 12,
backgroundColor: '#6200EE',
borderRadius: 4,
elevation: 2,
},
androidButtonText: {
color: 'white',
textAlign: 'center',
},
defaultButton: {
padding: 12,
backgroundColor: '#333',
borderRadius: 4,
},
defaultButtonText: {
color: 'white',
textAlign: 'center',
},
});
Type Handling for Internationalization Support
In multilingual projects, TypeScript ensures the existence of translation keys:
// Defining translation resource types
type TranslationResources = {
en: {
welcome: string;
buttons: {
submit: string;
cancel: string;
};
errors: {
required: string;
email: string;
};
};
fr: {
welcome: string;
buttons: {
submit: string;
cancel: string;
};
errors: {
required: string;
email: string;
};
};
};
const resources: TranslationResources = {
en: {
welcome: 'Welcome',
buttons: {
submit: 'Submit',
cancel: 'Cancel',
},
errors: {
required: 'This field is required',
email: 'Please enter a valid email',
},
},
fr: {
welcome: 'Bienvenue',
buttons: {
submit: 'Soumettre',
cancel: 'Annuler',
},
errors: {
required: 'Ce champ est obligatoire',
email: 'Veuillez entrer un email valide',
},
},
};
// Creating type-safe translation hook
function useTranslation(locale: keyof TranslationResources) {
const t = (key: string, options?: Record<string, string | number>) => {
// Implement translation logic
};
return { t };
}
// Usage example
const WelcomeScreen = () => {
const { t } = useTranslation('en');
return (
<View>
<Text>{t('welcome')}</Text>
<Button title={t('buttons.submit')} />
</View>
);
};
Error Boundaries and Type Safety
When implementing
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与Electron桌面开发
下一篇:与Deno运行时