React Native / TypeScript React Native-д

TypeScript React Native-д

TypeScript бол JavaScript дээр төрлийн (type) систем нэмсэн хэл. Код бичих үед алдааг илрүүлдэг тул олон цагийн дибагнаасаа аврагддаг. Expo апп үүсгэхэд TypeScript автоматаар тохируулагдсан байдаг — бид зөвхөн хэрхэн зөв ашиглахыг сурахад л хангалттай.

Үндсэн type-ууд React Native-д

Props-т type тодорхойлох нь хамгийн түгээмэл хэрэглээ. interface эсвэл type хоёрыг ашиглаж болно:

typescript
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

// Props-н interface тодорхойлно
interface ButtonProps {
  label: string;
  onPress: () => void;
  color?: string;       // ? = заавал биш
  disabled?: boolean;
  size?: 'sm' | 'md' | 'lg'; // Union type — зөвхөн эдгээр утга
}

export function AppButton({
  label,
  onPress,
  color = '#22d3ee',   // Default утга
  disabled = false,
  size = 'md',
}: ButtonProps) {
  const fontSize = size === 'sm' ? 14 : size === 'lg' ? 20 : 16;

  return (
    <TouchableOpacity
      style={[styles.button, { backgroundColor: disabled ? '#475569' : color }]}
      onPress={onPress}
      disabled={disabled}
    >
      <Text style={[styles.label, { fontSize }]}>{label}</Text>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  button: { paddingVertical: 12, paddingHorizontal: 24, borderRadius: 10, alignItems: 'center' },
  label: { color: '#0b1120', fontWeight: 'bold' },
});

React Navigation ашиглаж байгаа бол route params-т type нэмэх нь маш чухал — алдааг эрт гаргаж өгдөг:

typescript
import { NativeStackScreenProps } from '@react-navigation/native-stack';

// Бүх screen-н params-г нэг дор тодорхойлно
type RootStackParamList = {
  Home: undefined;                          // params байхгүй
  CourseDetail: { slug: string; isFree: boolean };
  LessonDetail: { courseSlug: string; lessonSlug: string; order: number };
  Profile: { userId?: string };             // заавал биш param
};

// Screen component props автоматаар type авна
type CourseDetailProps = NativeStackScreenProps<RootStackParamList, 'CourseDetail'>;

export default function CourseDetailScreen({ route, navigation }: CourseDetailProps) {
  // route.params.slug — TypeScript мэднэ, autocomplete ажиллана
  const { slug, isFree } = route.params;

  function goToLesson(lessonSlug: string) {
    navigation.navigate('LessonDetail', {
      courseSlug: slug,
      lessonSlug,
      order: 1,
    });
  }

  return (
    <View style={{ flex: 1, backgroundColor: '#0b1120', padding: 20 }}>
      <Text style={{ color: '#f1f5f9', fontSize: 24 }}>{slug}</Text>
      {isFree && <Text style={{ color: '#4ade80' }}>Үнэгүй</Text>}
    </View>
  );
}

Custom hook-д TypeScript

Hook-ийн буцаах утгыг тодорхой зааж өгөх нь ашиглах талаас нь ойлгомжтой болгодог:

typescript
import { useState, useEffect } from "react";

interface Lesson {
  slug: string;
  title: string;
  order: number;
  completed: boolean;
}

interface UseLessonsReturn {
  lessons: Lesson[];
  loading: boolean;
  error: string | null;
  refetch: () => void;
}

// Generic ашигласнаар дурын өгөгдлийн төрлийг дэмжих hook
function useFetch<T>(url: string): {
  data: T | null;
  loading: boolean;
  error: string | null;
} {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error("Сервер алдаа гарлаа");
        return res.json() as Promise<T>;
      })
      .then(setData)
      .catch((err: Error) => setError(err.message))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

// Ашиглах
export function useLessons(courseSlug: string): UseLessonsReturn {
  const { data, loading, error } = useFetch<Lesson[]>(
    `https://api.example.com/courses/${courseSlug}/lessons`,
  );

  function refetch() {
    // дахин fetch хийх логик
  }

  return {
    lessons: data ?? [],
    loading,
    error,
    refetch,
  };
}

AsyncStorage ба Supabase-тай TypeScript

Third-party library-тай ажиллахад type annotation нэмэх нь алдааг бууруулдаг:

typescript
import AsyncStorage from "@react-native-async-storage/async-storage";

// Generic функц — хадгалах өгөгдлийн төрлийг caller тодорхойлно
async function saveData<T>(key: string, value: T): Promise<void> {
  try {
    await AsyncStorage.setItem(key, JSON.stringify(value));
  } catch (error) {
    console.error("Хадгалах алдаа:", error);
  }
}

async function loadData<T>(key: string): Promise<T | null> {
  try {
    const raw = await AsyncStorage.getItem(key);
    return raw ? (JSON.parse(raw) as T) : null;
  } catch {
    return null;
  }
}

// Хэрэглэх — TypeScript автоматаар төрлийг мэднэ
interface UserSettings {
  theme: "dark" | "light";
  language: "mn" | "en";
  fontSize: number;
}

await saveData<UserSettings>("settings", {
  theme: "dark",
  language: "mn",
  fontSize: 16,
});

const settings = await loadData<UserSettings>("settings");
// settings?.theme → TypeScript: 'dark' | 'light' гэдгийг мэднэ

TypeScript эхэндээ нэмэлт ажил мэт санагддаг боловч апп томорч, баг гишүүд нэмэгдэх тусам асар их цаг хугацаа хэмнэдэг. Өнөөдрөөс дадал болгоорой!

Дараагийн хичээлд:

Тест бичих (Jest + RNTL) — аппынхаа component, hook, логикийг автоматаар шалгах тест бичих аргыг сурна. Тест нь "ажиллаж байна" гэдгийг баталгаажуулах хамгийн найдвартай арга.