react-native
90
总安装量
89
周安装量
#2545
全站排名
安装命令
npx skills add https://github.com/alinaqi/claude-bootstrap --skill react-native
Agent 安装分布
claude-code
74
opencode
68
gemini-cli
65
antigravity
60
cursor
59
codex
57
Skill 文档
React Native Skill
Load with: base.md + typescript.md
Project Structure
project/
âââ src/
â âââ core/ # Pure business logic (no React)
â â âââ types.ts
â â âââ services/
â âââ components/ # Reusable UI components
â â âââ Button/
â â â âââ Button.tsx
â â â âââ Button.test.tsx
â â â âââ index.ts
â â âââ index.ts # Barrel export
â âââ screens/ # Screen components
â â âââ Home/
â â â âââ HomeScreen.tsx
â â â âââ useHome.ts # Screen-specific hook
â â â âââ index.ts
â â âââ index.ts
â âââ navigation/ # Navigation configuration
â âââ hooks/ # Shared custom hooks
â âââ store/ # State management
â âââ utils/ # Utilities
âââ __tests__/
âââ android/
âââ ios/
âââ CLAUDE.md
Component Patterns
Functional Components Only
// Good - simple, testable
interface ButtonProps {
label: string;
onPress: () => void;
disabled?: boolean;
}
export function Button({ label, onPress, disabled = false }: ButtonProps): JSX.Element {
return (
<Pressable onPress={onPress} disabled={disabled}>
<Text>{label}</Text>
</Pressable>
);
}
Extract Logic to Hooks
// useHome.ts - all logic here
export function useHome() {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(false);
const refresh = useCallback(async () => {
setLoading(true);
const data = await fetchItems();
setItems(data);
setLoading(false);
}, []);
return { items, loading, refresh };
}
// HomeScreen.tsx - pure presentation
export function HomeScreen(): JSX.Element {
const { items, loading, refresh } = useHome();
return (
<ItemList items={items} loading={loading} onRefresh={refresh} />
);
}
Props Interface Always Explicit
// Always define props interface, even if simple
interface ItemCardProps {
item: Item;
onPress: (id: string) => void;
}
export function ItemCard({ item, onPress }: ItemCardProps): JSX.Element {
...
}
State Management
Local State First
// Start with useState, escalate only when needed
const [value, setValue] = useState('');
Zustand for Global State (if needed)
// store/useAppStore.ts
import { create } from 'zustand';
interface AppState {
user: User | null;
setUser: (user: User | null) => void;
}
export const useAppStore = create<AppState>((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
React Query for Server State
// hooks/useItems.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useItems() {
return useQuery({
queryKey: ['items'],
queryFn: fetchItems,
});
}
export function useCreateItem() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createItem,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['items'] });
},
});
}
Testing
Component Testing with React Native Testing Library
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';
describe('Button', () => {
it('calls onPress when pressed', () => {
const onPress = jest.fn();
const { getByText } = render(<Button label="Click me" onPress={onPress} />);
fireEvent.press(getByText('Click me'));
expect(onPress).toHaveBeenCalledTimes(1);
});
it('does not call onPress when disabled', () => {
const onPress = jest.fn();
const { getByText } = render(<Button label="Click me" onPress={onPress} disabled />);
fireEvent.press(getByText('Click me'));
expect(onPress).not.toHaveBeenCalled();
});
});
Hook Testing
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
Platform-Specific Code
Use Platform.select Sparingly
import { Platform } from 'react-native';
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
},
android: {
elevation: 2,
},
}),
});
Separate Files for Complex Differences
Component/
âââ Component.tsx # Shared logic
âââ Component.ios.tsx # iOS-specific
âââ Component.android.tsx # Android-specific
âââ index.ts
React Native Anti-Patterns
- â Inline styles – use StyleSheet.create
- â Logic in render – extract to hooks
- â Deep component nesting – flatten hierarchy
- â Anonymous functions in props – use useCallback
- â Index as key in lists – use stable IDs
- â Direct state mutation – always use setter
- â Mixing business logic with UI – keep core/ pure
- â Ignoring TypeScript errors – fix them
- â Large components – split into smaller pieces