用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/miles990/claude-software-skills --skill mobile命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | mobile |
| description | Mobile development with React Native, Flutter, and native patterns |
| domain | development-stacks |
| version | 1.0.0 |
| tags | ["react-native","flutter","ios","android","expo","mobile-ui"] |
| triggers | {"keywords":{"primary":["mobile","react native","flutter","ios","android","app","expo"],"secondary":["navigation","push notification","deep link","gesture","native module"]},"context_boost":["app store","play store","cross-platform","hybrid"],"context_penalty":["web","backend","desktop","server"],"priority":"high"} |
Cross-platform and native mobile development patterns, frameworks, and best practices.
// Functional component with hooks
import React, { useState, useCallback } from 'react';
import {
View,
Text,
FlatList,
TouchableOpacity,
StyleSheet,
RefreshControl,
} from 'react-native';
interface User {
id: string;
name: string;
email: string;
}
interface UserListProps {
users: User[];
onSelect: (user: User) => void;
onRefresh: () => Promise<void>;
}
export function UserList({ users, onSelect, onRefresh }: UserListProps) {
const [refreshing, setRefreshing] = useState(false);
const handleRefresh = useCallback(async () => {
setRefreshing(true);
await onRefresh();
setRefreshing(false);
}, [onRefresh]);
const renderItem = useCallback(({ item }: { item: User }) => (
<TouchableOpacity
style={styles.item}
onPress={() => onSelect(item)}
activeOpacity={0.7}
>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.email}>{item.email}</Text>
</TouchableOpacity>
), [onSelect]);
const keyExtractor = useCallback((item: User) => item.id, []);
return (
<FlatList
data={users}
renderItem={renderItem}
keyExtractor={keyExtractor}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>
}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListEmptyComponent={
<Text style={styles.empty}>No users found</Text>
}
/>
);
}
const styles = StyleSheet.create({
item: {
padding: 16,
backgroundColor: '#fff',
},
name: {
fontSize: 16,
fontWeight: '600',
color: '#1a1a1a',
},
email: {
fontSize: 14,
color: '#666',
marginTop: 4,
},
separator: {
height: 1,
backgroundColor: '#e0e0e0',
},
empty: {
textAlign: 'center',
padding: 32,
color: '#999',
},
});
// React Navigation setup
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
// Type-safe navigation
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
};
type TabParamList = {
Feed: undefined;
Search: undefined;
Notifications: undefined;
Account: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<TabParamList>();
function TabNavigator() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
const iconName = {
Feed: focused ? 'home' : 'home-outline',
Search: focused ? 'search' : 'search-outline',
Notifications: focused ? 'bell' : 'bell-outline',
Account: focused ? 'person' : 'person-outline',
}[route.name];
return ;
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: '#8E8E93',
})}
>
);
}
() {
(
);
}
{ useNavigation, useRoute } ;
{ } ;
= <
,
>;
() {
navigation = useNavigation<>();
(
);
}
// Zustand for React Native
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AuthState {
user: User | null;
token: string | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,
login: async (email, password) => {
const response = await api.login(email, password);
set({ user: response.user, token: response.token });
},
logout: () => {
({ : , : });
},
}),
{
: ,
: ( ),
}
)
);
{ useQuery, useMutation, } ;
queryClient = ({
: {
: {
: ,
: * * ,
},
},
});
() {
({
: [],
: api.(),
});
}
() {
({
: api.(newPost),
: {
queryClient.({ : [] });
},
});
}
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 44 : 0,
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 4,
},
}),
},
});
// Platform-specific files
// Button.ios.tsx
// Button.android.tsx
// Import as: import { Button } from './Button';
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>
);
}
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: 'Home',
({ }) => (
),
}}
/>
(
),
}}
/>
);
}
{ , } ;
{ } ;
() {
(
);
}
import * as Camera from 'expo-camera';
import * as ImagePicker from 'expo-image-picker';
import * as Location from 'expo-location';
import * as Notifications from 'expo-notifications';
// Camera
async function takePhoto() {
const { status } = await Camera.requestCameraPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Permission required', 'Camera access is needed');
return;
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.8,
});
if (!result.canceled) {
return result.assets[0].uri;
}
}
() {
{ status } = .();
(status !== ) {
();
}
location = .({});
{
: location..,
: location..,
};
}
() {
{ status } = .();
(status !== ) {
;
}
token = .();
token.;
}
.({
: () => ({
: ,
: ,
: ,
}),
});
// Stateless widget
class UserCard extends StatelessWidget {
final User user;
final VoidCallback onTap;
const UserCard({
super.key,
required this.user,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(user.avatarUrl),
),
title: Text(user.name),
subtitle: Text(user.email),
trailing: const Icon(Icons.chevron_right),
onTap: onTap,
),
);
}
}
// Stateful widget with hooks (flutter_hooks)
class CounterPage extends HookWidget {
@override
Widget build(BuildContext context) {
final count = useState(0);
final controller = useAnimationController(duration: Duration(seconds: 1));
return Scaffold(
body: Center(
child: Text('Count: ${count.value}'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => count.value++,
child: const Icon(Icons.add),
),
);
}
}
// Provider definitions
final userProvider = FutureProvider<User>((ref) async {
final repository = ref.watch(userRepositoryProvider);
return repository.getCurrentUser();
});
final userRepositoryProvider = Provider((ref) {
return UserRepository(ref.watch(dioProvider));
});
// State notifier
class CartNotifier extends StateNotifier<List<CartItem>> {
CartNotifier() : super([]);
void add(CartItem item) {
state = [...state, item];
}
void remove(String id) {
state = state.where((item) => item.id != id).toList();
}
double get total => state.fold(0, (sum, item) => sum + item.price);
}
final cartProvider = StateNotifierProvider<CartNotifier, List<CartItem>>((ref) {
return CartNotifier();
});
// Using providers
class CartPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(cartProvider);
final notifier = ref.read(cartProvider.notifier);
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => notifier.remove(item.id),
),
);
},
);
}
}
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'profile/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ProfileScreen(userId: id);
},
),
],
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
],
redirect: (context, state) {
final isLoggedIn = ref.read(authProvider).isLoggedIn;
final isLoggingIn = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoggingIn) {
return '/login';
}
if (isLoggedIn && isLoggingIn) {
return '/';
}
return null;
},
);
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
);
}
}
import { useWindowDimensions } from 'react-native';
function ResponsiveLayout({ children }) {
const { width } = useWindowDimensions();
const isTablet = width >= 768;
return (
<View style={isTablet ? styles.tabletContainer : styles.phoneContainer}>
{children}
</View>
);
}
// Safe area handling
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
function Screen({ children }) {
const insets = useSafeAreaInsets();
return (
<View style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}>
{children}
</View>
);
}
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
function DraggableCard() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const pan = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
translateY.value = e.translationY;
})
.onEnd(() => {
translateX.value = withSpring(0);
translateY.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
],
}));
return (
<GestureDetector gesture={pan}>
Drag me!
);
}