重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
react-native by gentleman-programming/gentleman-skills
npx skills add https://github.com/gentleman-programming/gentleman-skills --skill react-native在以下情况下加载此技能:
src/
├── app/ # Expo Router 屏幕(如果使用)
│ ├── (tabs)/ # 标签导航器分组
│ ├── (auth)/ # 认证流程分组
│ └── _layout.tsx # 根布局
├── components/
│ ├── ui/ # 可复用的 UI 组件
│ └── features/ # 特定功能组件
├── hooks/ # 自定义钩子
├── services/ # API 和外部服务
├── stores/ # 状态管理(Zustand)
├── utils/ # 工具函数
├── constants/ # 应用常量、主题
└── types/ # TypeScript 类型
始终使用具有适当类型的函数式组件:
import { View, Text, Pressable } from 'react-native';
import type { ViewStyle, TextStyle } from 'react-native';
interface ButtonProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
export function Button({
title,
onPress,
variant = 'primary',
disabled = false
}: ButtonProps) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.button,
variant === 'secondary' && styles.buttonSecondary,
pressed && styles.buttonPressed,
disabled && styles.buttonDisabled,
]}
>
<Text style={styles.buttonText}>{title}</Text>
</Pressable>
);
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
使用 Platform 模块或文件扩展名来处理平台特定代码:
import { Platform, StyleSheet } from 'react-native';
// 使用 Platform.select
const styles = StyleSheet.create({
container: {
paddingTop: Platform.select({
ios: 44,
android: 0,
}),
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 4,
},
}),
},
});
// 或者使用文件扩展名:
// Component.ios.tsx
// Component.android.tsx
// app/_layout.tsx
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
export default function RootLayout() {
return (
<>
<StatusBar style="auto" />
<Stack
screenOptions={{
headerShown: false,
animation: 'slide_from_right',
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{
presentation: 'modal',
animation: 'slide_from_bottom',
}}
/>
</Stack>
</>
);
}
// hooks/useUser.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userService } from '@/services/user';
import type { User, UpdateUserInput } from '@/types';
export function useUser(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => userService.getById(userId),
staleTime: 5 * 60 * 1000, // 5 分钟
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdateUserInput) => userService.update(data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
},
});
}
// With NativeWind (Tailwind for React Native)
import { View, Text, Pressable } from 'react-native';
import { styled } from 'nativewind';
const StyledPressable = styled(Pressable);
const StyledView = styled(View);
const StyledText = styled(Text);
export function Card({ title, description, onPress }: CardProps) {
return (
<StyledPressable
className="bg-white dark:bg-gray-800 rounded-2xl p-4 shadow-md active:scale-95"
onPress={onPress}
>
<StyledView className="flex-row items-center gap-3">
<StyledView className="w-12 h-12 bg-blue-100 dark:bg-blue-900 rounded-full items-center justify-center">
<StyledText className="text-blue-600 dark:text-blue-300 text-xl">
📱
</StyledText>
</StyledView>
<StyledView className="flex-1">
<StyledText className="text-lg font-semibold text-gray-900 dark:text-white">
{title}
</StyledText>
<StyledText className="text-sm text-gray-500 dark:text-gray-400">
{description}
</StyledText>
</StyledView>
</StyledView>
</StyledPressable>
);
}
import { KeyboardAvoidingView, Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export function ScreenWrapper({ children }: { children: React.ReactNode }) {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top', 'left', 'right']}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
>
{children}
</KeyboardAvoidingView>
</SafeAreaView>
);
}
// stores/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AuthState {
token: string | null;
user: User | null;
isAuthenticated: boolean;
login: (token: string, user: User) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
user: null,
isAuthenticated: false,
login: (token, user) => set({ token, user, isAuthenticated: true }),
logout: () => set({ token: null, user: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// ❌ 不好 - 内联样式难以维护且不会记忆化
export function BadComponent() {
return (
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', color: '#333' }}>
Title
</Text>
</View>
);
}
// ✅ 好 - 使用 StyleSheet 或 NativeWind
const styles = StyleSheet.create({
container: { flex: 1, padding: 16, backgroundColor: '#fff' },
title: { fontSize: 18, fontWeight: 'bold', color: '#333' },
});
export function GoodComponent() {
return (
<View style={styles.container}>
<Text style={styles.title}>Title</Text>
</View>
);
}
// ❌ 不好 - TouchableOpacity 是旧版 API
import { TouchableOpacity } from 'react-native';
// ✅ 好 - 使用具有反馈的 Pressable
import { Pressable } from 'react-native';
<Pressable
onPress={onPress}
style={({ pressed }) => [
styles.button,
pressed && { opacity: 0.7 }
]}
>
{({ pressed }) => (
<Text style={pressed ? styles.textPressed : styles.text}>
Press Me
</Text>
)}
</Pressable>
// ❌ 不好 - 没有加载/错误处理
export function UserProfile({ userId }: { userId: string }) {
const { data } = useUser(userId);
return <Text>{data.name}</Text>; // 如果 data 是 undefined 会崩溃
}
// ✅ 好 - 处理所有状态
export function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;
return <Text>{data.name}</Text>;
}
| 任务 | 模式 |
|---|---|
| 创建新的 Expo 项目 | npx create-expo-app@latest --template tabs |
| 添加 NativeWind | npx expo install nativewind tailwindcss |
| 平台检查 | Platform.OS === 'ios' |
| 安全边距 | 使用 react-native-safe-area-context 中的 useSafeAreaInsets() |
| 导航 | 使用 Expo Router 的 router.push('/screen') |
| 深度链接 | 在 app.json 的 expo.scheme 下配置 |
| 环境变量 | 使用 expo-constants 或 react-native-dotenv |
| 图标 | @expo/vector-icons(包含在 Expo 中) |
| 动画 | 使用 react-native-reanimated 实现 60fps 动画 |
| 手势 | 使用 react-native-gesture-handler |
每周安装量
70
代码仓库
GitHub 星标数
379
首次出现
2026年1月24日
安全审计
已安装于
opencode62
codex52
gemini-cli50
cursor50
github-copilot49
claude-code47
Load this skill when:
src/
├── app/ # Expo Router screens (if using)
│ ├── (tabs)/ # Tab navigator group
│ ├── (auth)/ # Auth flow group
│ └── _layout.tsx # Root layout
├── components/
│ ├── ui/ # Reusable UI components
│ └── features/ # Feature-specific components
├── hooks/ # Custom hooks
├── services/ # API and external services
├── stores/ # State management (Zustand)
├── utils/ # Utility functions
├── constants/ # App constants, themes
└── types/ # TypeScript types
Always use functional components with proper typing:
import { View, Text, Pressable } from 'react-native';
import type { ViewStyle, TextStyle } from 'react-native';
interface ButtonProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
export function Button({
title,
onPress,
variant = 'primary',
disabled = false
}: ButtonProps) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.button,
variant === 'secondary' && styles.buttonSecondary,
pressed && styles.buttonPressed,
disabled && styles.buttonDisabled,
]}
>
<Text style={styles.buttonText}>{title}</Text>
</Pressable>
);
}
Use Platform module or file extensions for platform-specific code:
import { Platform, StyleSheet } from 'react-native';
// Using Platform.select
const styles = StyleSheet.create({
container: {
paddingTop: Platform.select({
ios: 44,
android: 0,
}),
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 4,
},
}),
},
});
// Or use file extensions:
// Component.ios.tsx
// Component.android.tsx
// app/_layout.tsx
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
export default function RootLayout() {
return (
<>
<StatusBar style="auto" />
<Stack
screenOptions={{
headerShown: false,
animation: 'slide_from_right',
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{
presentation: 'modal',
animation: 'slide_from_bottom',
}}
/>
</Stack>
</>
);
}
// hooks/useUser.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userService } from '@/services/user';
import type { User, UpdateUserInput } from '@/types';
export function useUser(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => userService.getById(userId),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdateUserInput) => userService.update(data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
},
});
}
// With NativeWind (Tailwind for React Native)
import { View, Text, Pressable } from 'react-native';
import { styled } from 'nativewind';
const StyledPressable = styled(Pressable);
const StyledView = styled(View);
const StyledText = styled(Text);
export function Card({ title, description, onPress }: CardProps) {
return (
<StyledPressable
className="bg-white dark:bg-gray-800 rounded-2xl p-4 shadow-md active:scale-95"
onPress={onPress}
>
<StyledView className="flex-row items-center gap-3">
<StyledView className="w-12 h-12 bg-blue-100 dark:bg-blue-900 rounded-full items-center justify-center">
<StyledText className="text-blue-600 dark:text-blue-300 text-xl">
📱
</StyledText>
</StyledView>
<StyledView className="flex-1">
<StyledText className="text-lg font-semibold text-gray-900 dark:text-white">
{title}
</StyledText>
<StyledText className="text-sm text-gray-500 dark:text-gray-400">
{description}
</StyledText>
</StyledView>
</StyledView>
</StyledPressable>
);
}
import { KeyboardAvoidingView, Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export function ScreenWrapper({ children }: { children: React.ReactNode }) {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top', 'left', 'right']}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
>
{children}
</KeyboardAvoidingView>
</SafeAreaView>
);
}
// stores/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AuthState {
token: string | null;
user: User | null;
isAuthenticated: boolean;
login: (token: string, user: User) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
user: null,
isAuthenticated: false,
login: (token, user) => set({ token, user, isAuthenticated: true }),
logout: () => set({ token: null, user: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// ❌ Bad - inline styles are hard to maintain and don't memoize
export function BadComponent() {
return (
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', color: '#333' }}>
Title
</Text>
</View>
);
}
// ✅ Good - use StyleSheet or NativeWind
const styles = StyleSheet.create({
container: { flex: 1, padding: 16, backgroundColor: '#fff' },
title: { fontSize: 18, fontWeight: 'bold', color: '#333' },
});
export function GoodComponent() {
return (
<View style={styles.container}>
<Text style={styles.title}>Title</Text>
</View>
);
}
// ❌ Bad - TouchableOpacity is legacy
import { TouchableOpacity } from 'react-native';
// ✅ Good - Use Pressable with feedback
import { Pressable } from 'react-native';
<Pressable
onPress={onPress}
style={({ pressed }) => [
styles.button,
pressed && { opacity: 0.7 }
]}
>
{({ pressed }) => (
<Text style={pressed ? styles.textPressed : styles.text}>
Press Me
</Text>
)}
</Pressable>
// ❌ Bad - no loading/error handling
export function UserProfile({ userId }: { userId: string }) {
const { data } = useUser(userId);
return <Text>{data.name}</Text>; // Will crash if data is undefined
}
// ✅ Good - handle all states
export function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;
return <Text>{data.name}</Text>;
}
| Task | Pattern |
|---|---|
| Create new Expo project | npx create-expo-app@latest --template tabs |
| Add NativeWind | npx expo install nativewind tailwindcss |
| Platform check | Platform.OS === 'ios' |
| Safe insets | useSafeAreaInsets() from react-native-safe-area-context |
| Navigation | router.push('/screen') with Expo Router |
Weekly Installs
70
Repository
GitHub Stars
379
First Seen
Jan 24, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode62
codex52
gemini-cli50
cursor50
github-copilot49
claude-code47
Tailwind CSS v4 + shadcn/ui 生产级技术栈配置指南与最佳实践
2,600 周安装
| Deep linking | Configure in app.json under expo.scheme |
| Environment vars | Use expo-constants or react-native-dotenv |
| Icons | @expo/vector-icons (included in Expo) |
| Animations | react-native-reanimated for 60fps animations |
| Gestures | react-native-gesture-handler |