مقالات › موبایل و دسکتاپ

شروع کار با React Native و Expo؛ ساخت اولین اپلیکیشن موبایل

ساخت اولین اپلیکیشن موبایل با React Native و Expo: کامپوننت‌ها و استایل، ناوبری با Expo Router، اتصال به API، راست‌چین و انتشار در فروشگاه‌ها.

اگر React بلدید، بیشتر مسیر ساخت اپلیکیشن موبایل بومی (Native) را رفته‌اید. React Native اجازه می‌دهد اپلیکیشن‌های iOS و اندروید را با همان کامپوننت‌ها، Hookها و مدیریت State که در وب استفاده می‌کنید بنویسید؛ و نتیجه، رابط کاربری واقعاً بومی است، نه یک WebView. با Expo راه‌اندازی چند دقیقه طول می‌کشد. این راهنما ملزومات ساخت اولین اپلیکیشن واقعی را مرور می‌کند.

React Native چطور کار می‌کند

کامپوننت‌های React را با جاوااسکریپت یا تایپ‌اسکریپت می‌نویسید. React Native به‌جای رندر <div> و <span> در DOM، ویوهای بومی رندر می‌کند: <View> در اندروید به ViewGroup و در iOS به UIView تبدیل می‌شود و <Text> به یک Text View بومی. از زمان معماری جدید (Fabric و TurboModules که در نسخه‌های اخیر پیش‌فرض است)، جاوااسکریپت از طریق یک رابط سریع و همگام (JSI) با کد بومی ارتباط می‌گیرد، نه یک پل JSON.

آنچه با React وب مشترک است: کامپوننت‌ها، Hookها، Context، React Query، Zustand/Redux، کتابخانه‌های اعتبارسنجی و بیشتر منطق کسب‌وکار. آنچه متفاوت است: فایل HTML و CSS وجود ندارد، کامپوننت‌ها فرق دارند و APIهای سکو (دوربین، اعلان، ذخیره‌سازی) جدا هستند.

راه‌اندازی با Expo

npx create-expo-app@latest shop-app
cd shop-app
npx expo start

اپلیکیشن Expo Go را روی گوشی نصب و QR Code را اسکن کنید؛ برنامه فوراً روی دستگاه اجرا می‌شود و با هر ذخیره، Hot Reload دارد. برای امکاناتی که به کد بومی سفارشی نیاز دارند بعداً یک Development Build می‌سازید، اما Expo Go برای یادگیری عالی است.

نکته: Expo روش پیشنهادی رسمی برای شروع پروژه‌ی React Native است و شما را محدود نمی‌کند: می‌توانید هر کتابخانه‌ی بومی را اضافه کنید و همچنان با ابزارهای استاندارد Build بگیرید.

کامپوننت‌های اصلی

وب React Native توضیح
<div> <View> نگه‌دارنده‌ی چیدمان
<span>، <p> <Text> همه‌ی متن‌ها باید داخل <Text> باشند
<img> <Image> (یا expo-image) تصاویر اینترنتی به اندازه‌ی صریح نیاز دارند
<input> <TextInput> onChangeText به‌جای onChange
<button> <Pressable> کنترل کامل روی استایل حالت فشرده
div اسکرول‌دار <ScrollView> برای محتوای کوتاه
لیست طولانی <FlatList> مجازی‌سازی‌شده؛ برای هر لیست بلند
import { View, Text, Pressable, StyleSheet } from "react-native";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <View style={styles.card}>
      <Text style={styles.title}>شمارنده: {count}</Text>
      <Pressable
        onPress={() => setCount((c) => c + 1)}
        style={({ pressed }) => [styles.button, pressed && { opacity: 0.7 }]}
      >
        <Text style={styles.buttonText}>یکی اضافه کن</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  card: { padding: 20, borderRadius: 16, backgroundColor: "#fff", gap: 12 },
  title: { fontSize: 20, fontWeight: "600", color: "#13121f" },
  button: { backgroundColor: "#7c3aed", paddingVertical: 12, borderRadius: 12, alignItems: "center" },
  buttonText: { color: "#fff", fontWeight: "600" },
});

استایل‌دهی و چیدمان

  • استایل‌ها اشیای جاوااسکریپت با نام‌های camelCase هستند (backgroundColor، marginTop).
  • واحدها پیکسل مستقل از تراکم هستند؛ برای بیشتر ویژگی‌ها px، rem یا % نداریم (درصد برای عرض و ارتفاع کار می‌کند).
  • سیستم چیدمان Flexbox است، اما مقدار پیش‌فرض flexDirection برابر column است.
  • آبشاری (Cascade) وجود ندارد: استایل‌ها ارث نمی‌رسند، جز استایل متن در <Text>های تودرتو.
  • از SafeAreaView (از react-native-safe-area-context) استفاده کنید تا محتوا زیر ناچ و نوار پایین نرود.

اگر عاشق Tailwind هستید، NativeWind کلاس‌های Utility را به React Native می‌آورد.

لیست به روش درست

<FlatList
  data={products}
  keyExtractor={(item) => String(item.id)}
  renderItem={({ item }) => <ProductRow product={item} />}
  ItemSeparatorComponent={() => <View style={{ height: 8 }} />}
  onEndReached={loadMore}
  onEndReachedThreshold={0.5}
  refreshing={isRefreshing}
  onRefresh={refresh}
  ListEmptyComponent={<Text>محصولی پیدا نشد.</Text>}
/>

FlatList فقط ردیف‌های قابل‌مشاهده را رندر می‌کند و اسکرول بی‌نهایت و کشیدن برای بازخوانی (Pull to Refresh) را به‌صورت پیش‌فرض دارد. هرگز صدها آیتم را با .map() داخل ScrollView رندر نکنید.

ناوبری با Expo Router

Expo Router از مسیریابی مبتنی بر فایل استفاده می‌کند؛ مشابه Next.js:

app/
  _layout.jsx          → Stack اصلی
  (tabs)/
    _layout.jsx        → تب‌های پایین
    index.jsx          → تب خانه
    orders.jsx         → تب سفارش‌ها
  product/[id].jsx     → جزئیات محصول
// app/(tabs)/_layout.jsx
import { Tabs } from "expo-router";
import { Ionicons } from "@expo/vector-icons";

export default function TabsLayout() {
  return (
    <Tabs screenOptions={{ tabBarActiveTintColor: "#7c3aed" }}>
      <Tabs.Screen name="index" options={{ title: "خانه", tabBarIcon: ({ color }) => <Ionicons name="home" size={22} color={color} /> }} />
      <Tabs.Screen name="orders" options={{ title: "سفارش‌ها", tabBarIcon: ({ color }) => <Ionicons name="receipt" size={22} color={color} /> }} />
    </Tabs>
  );
}
// app/product/[id].jsx
import { useLocalSearchParams, Stack } from "expo-router";

export default function ProductScreen() {
  const { id } = useLocalSearchParams();
  const { data } = useProduct(id);
  return (
    <>
      <Stack.Screen options={{ title: data?.name ?? "محصول" }} />
      <ProductDetails product={data} />
    </>
  );
}

// ناوبری از هر جا
<Link href={`/product/${item.id}`}>مشاهده</Link>

مسیرها خودکار Deep Link هم هستند (shopapp://product/42).

اتصال به API در ASP.NET Core

fetch دقیقاً مثل وب کار می‌کند و React Query کش، تلاش مجدد و وضعیت بارگذاری را اضافه می‌کند:

const API = process.env.EXPO_PUBLIC_API_URL;   // مثلاً https://api.example.com

export function useProducts() {
  return useQuery({
    queryKey: ["products"],
    queryFn: async () => {
      const res = await fetch(`${API}/api/products`);
      if (!res.ok) throw new Error("Failed to load products");
      return res.json();
    },
  });
}

تفاوت‌ها با وب:

  • CORS اعمال نمی‌شود؛ اپلیکیشن موبایل مرورگر نیست. API شما همچنان به احراز هویت درست نیاز دارد.
  • localhost روی گوشی یعنی خود گوشی. در توسعه از IP شبکه‌ی محلی کامپیوترتان استفاده کنید (http://192.168.1.10:5179) و HTTP را فقط برای توسعه مجاز کنید.
  • APIهای تولید باید HTTPS باشند (iOS به‌طور پیش‌فرض HTTP ساده را مسدود می‌کند).
  • شبکه‌ی قطع یا کند را مدیریت کنید: داده‌ی کش‌شده نشان دهید، دوباره تلاش کنید و وضعیت خطای واضح بدهید.

ذخیره‌ی داده و توکن

  • اطلاعات محرمانه (Access/Refresh Token): expo-secure-store که از Keychain در iOS و Keystore در اندروید استفاده می‌کند.
  • تنظیمات و داده‌ی کوچک: @react-native-async-storage/async-storage.
  • داده‌ی ساختاریافته‌ی بزرگ‌تر یا آفلاین: expo-sqlite.
import * as SecureStore from "expo-secure-store";

await SecureStore.setItemAsync("refreshToken", token);
const saved = await SecureStore.getItemAsync("refreshToken");

هشدار: هرگز توکن را در AsyncStorage ذخیره نکنید؛ رمزنگاری نشده است. و هرگز کلیدهای محرمانه‌ی API را در باندل اپلیکیشن قرار ندهید؛ هر چیزی که همراه اپ منتشر شود قابل استخراج است.

راست‌چین و اپلیکیشن‌های فارسی

React Native از طریق I18nManager از چیدمان راست‌به‌چپ پشتیبانی می‌کند:

import { I18nManager } from "react-native";

I18nManager.allowRTL(true);
I18nManager.forceRTL(true);   // بعد از بارگذاری مجدد اپ اعمال می‌شود

با فعال بودن RTL، مقدار flexDirection: "row" از راست به چپ چیده می‌شود و marginStart/paddingEnd از جهت پیروی می‌کنند؛ آن‌ها را به marginLeft/marginRight ترجیح دهید. یک فونت فارسی (مثل وزیرمتن) را با expo-font بارگذاری و روی کامپوننت‌های Text اعمال کنید و برای متن‌های دوجهته از writingDirection: "rtl" استفاده کنید.

امکانات بومی با ماژول‌های Expo

Expo ماژول‌های به‌خوبی نگه‌داری‌شده‌ای برای نیازهای رایج دارد: expo-camera، expo-image-picker، expo-location، expo-notifications، expo-local-authentication (اثر انگشت/Face ID) و expo-haptics. همیشه مجوزها را با توضیح واضح درخواست کنید و حالت «رد شدن» را با ظرافت مدیریت کنید.

ساخت و انتشار

EAS (Expo Application Services) فایل‌های امضاشده را در فضای ابری می‌سازد؛ برای Build گرفتن iOS حتی به مک هم نیاز ندارید:

npm install -g eas-cli
eas login
eas build:configure
eas build --platform android      # فایل aab برای گوگل‌پلی (یا apk برای نصب مستقیم)
eas build --platform ios
eas submit --platform android
  • اندروید: حساب توسعه‌دهنده‌ی Google Play؛ برای کاربران ایرانی، کافه‌بازار و مایکت را هم در نظر بگیرید که همان APK/AAB را می‌پذیرند.
  • iOS: عضویت در Apple Developer Program.
  • به‌روزرسانی Over-the-Air (eas update) اصلاحات جاوااسکریپتی را بدون انتشار نسخه‌ی جدید در فروشگاه به کاربران می‌رساند؛ فقط برای تغییرات JS.

نکات کارایی

  • برای لیست‌ها از FlatList/FlashList استفاده و کامپوننت ردیف‌ها را Memo کنید.
  • اگر پروفایل‌گیری مشکل رندر مجدد نشان داد، از توابع ناشناس درون‌خطی در آیتم‌های پرتکرار لیست پرهیز کنید.
  • برای کش و بارگذاری نرم تصاویر از expo-image استفاده کنید.
  • انیمیشن‌ها را با react-native-reanimated روی ترد UI اجرا کنید.
  • نسخه‌ی Release را روی یک گوشی اندروید میان‌رده‌ی واقعی تست کنید؛ حالت توسعه بسیار کندتر از تولید است.

جمع‌بندی

React Native به توسعه‌دهندگان React اجازه می‌دهد اپلیکیشن‌های بومی واقعی برای iOS و اندروید بسازند. با Expo شروع کنید، کامپوننت‌های اصلی (View، Text، Pressable، FlatList) را یاد بگیرید، با Flexbox و StyleSheet استایل دهید، با Expo Router ناوبری کنید، API را با fetch و React Query صدا بزنید، توکن‌ها را در SecureStore نگه دارید و با EAS بسازید و منتشر کنید. بیشتر دانش React شما — و بخش زیادی از کد وب — مستقیماً منتقل می‌شود.

پرسش‌های متداول

React Native یا Flutter؟

هر دو عالی‌اند. اگر تیم شما React و جاوااسکریپت/تایپ‌اسکریپت بلد است و می‌خواهد منطق را با وب‌سایت به اشتراک بگذارد، React Native انتخاب طبیعی است؛ Flutter از Dart و موتور رندر اختصاصی خودش استفاده می‌کند.

آیا می‌توان کد را بین وب‌سایت React و اپ React Native به اشتراک گذاشت؟

بله؛ منطق کسب‌وکار، کلاینت API، Schemaهای اعتبارسنجی، Hookها و Storeهای State به‌خوبی مشترک می‌شوند. کامپوننت‌های UI معمولاً متفاوت‌اند، مگر از لایه‌ای مثل React Native Web استفاده کنید.

آیا باید Java، Kotlin یا Swift بلد باشم؟

برای شروع نه. ماژول‌های Expo بیشتر نیازها را پوشش می‌دهند. دانش بومی وقتی کمک می‌کند که ماژول بومی سفارشی بنویسید یا مشکلات Build خاص یک سکو را عیب‌یابی کنید.