React Native в 2026 — это Expo SDK 52+. Ты пишешь один код, тестируешь в Expo Go, собираешь через EAS — и получаешь на выходе нативные приложения для iOS и Android. Claude Code с этим управляется отлично, если сразу закрепить стек.
Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper
Старт
npx create-expo-app@latest my-app -t cd my-app npx expo install expo-router
Дальше в CLAUDE.md:
## Стек - Expo SDK 52, React Native 0.76 - expo-router (file-based navigation) - TanStack Query для API - Zustand для локального state - NativeWind (Tailwind для RN) - TypeScript strict ## Правила - Навигация — только через expo-router, никакого react-navigation напрямую - Стили — NativeWind classes (className), не StyleSheet.create - API — TanStack Query, не fetch в useEffect - Для нативных фич — только expo-* пакеты (не чистый native)
File-based роутинг
// app/_layout.tsx
import { Stack } from "expo-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="product/[id]" />
</Stack>
</QueryClientProvider>
);
}// app/(tabs)/_layout.tsx
import { Tabs } from "expo-router";
export default function TabsLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: "Лента" }} />
<Tabs.Screen name="profile" options={{ title: "Профиль" }} />
</Tabs>
);
}Файлы в app/ автоматически становятся роутами.
Список с pull-to-refresh
// app/(tabs)/index.tsx
import { FlatList, Text, View, RefreshControl } from "react-native";
import { useQuery } from "@tanstack/react-query";
import { Link } from "expo-router";
export default function Feed() {
const { data = [], refetch, isRefetching } = useQuery({
queryKey: ["products"],
queryFn: async () => {
const res = await fetch("https://api.example.com/products");
return res.json();
},
});
return (
<FlatList
className="flex-1 bg-white"
data={data}
keyExtractor={(p) => String(p.id)}
refreshControl={<RefreshControl refreshing={isRefetching} onRefresh={refetch} />}
renderItem={({ item }) => (
<Link href={`/product/${item.id}`} asChild>
<View className="p-4 border-b border-gray-200">
<Text className="text-lg font-semibold">{item.name}</Text>
<Text className="text-gray-600">{item.priceCents / 100} ₽</Text>
</View>
</Link>
)}
/>
);
}Безопасное хранилище
// lib/storage.ts
import * as SecureStore from "expo-secure-store";
export const storage = {
async setToken(token: string) {
await SecureStore.setItemAsync("auth_token", token);
},
async getToken() {
return SecureStore.getItemAsync("auth_token");
},
async clear() {
await SecureStore.deleteItemAsync("auth_token");
},
};expo-secure-store использует Keychain на iOS и Keystore на Android.
Сборка через EAS
npm i -g eas-cli eas build:configure eas build --platform ios --profile production eas build --platform android --profile production
eas.json:
{
"cli": { "version": ">= 14.0.0" },
"build": {
"development": { "developmentClient": true, "distribution": "internal" },
"preview": { "distribution": "internal" },
"production": {}
}
}Сборка идёт в облаке Expo — не нужен Mac для iOS.
Подводные камни
- Reanimated требует babel-plugin. Если анимации не работают — проверь
babel.config.js. - NativeWind и темы. Dark mode через
useColorScheme()+ префиксыdark:. - Image на Android кэшируется агрессивно. Для динамических URL добавляй
?t=...или используйexpo-image. - fetch без timeout может висеть. Всегда
AbortControllerс таймаутом.
Как попробовать
1. npx create-expo-app@latest 2. Установи Expo Go на телефон 3. npx expo start → скан QR → приложение живое 4. Правишь код в Claude — на телефоне обновляется мгновенно
Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper
