React Native / Гүйцэтгэлийн оновчлол

Гүйцэтгэлийн оновчлол

Апп удаан ажиллах, дэлгэц чичрэх, scroll хийхэд хоцрох — эдгээр нь хэрэглэгчийг зайлуулдаг хамгийн муу туршлага. React Native дээр гүйцэтгэлийн асуудлууд ихэвчлэн re-render хэт олон удаа болж байгаагаас үүсдэг. Цөөн өөрчлөлтөөр аппыг мэдэгдэхүйц хурдасгаж болно — яаж болохыг сурцгаая.

Re-render-г тань тодорхойлох

React дэх хамгийн түгээмэл гүйцэтгэлийн алдаа бол шаардлагагүй re-render. memo, useMemo, useCallback гурвыг зөв хэрэглэх нь асуудлыг ихэнхдээ шийдэж өгдөг.

React.memo нь component-н props өөрчлөгдөөгүй бол re-render хийхгүй:

typescript
import React, { memo, useState, useCallback } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

// memo ашиглахгүй бол parent re-render болох бүрт энэ ч render болно
const CourseCard = memo(function CourseCard({
  title,
  onPress,
}: {
  title: string;
  onPress: () => void;
}) {
  console.log('CourseCard render:', title); // Хэдэн удаа render болж байгааг ажиглана
  return (
    <TouchableOpacity style={styles.card} onPress={onPress}>
      <Text style={styles.title}>{title}</Text>
    </TouchableOpacity>
  );
});

export default function CourseList() {
  const [count, setCount] = useState(0);

  // useCallback ашиглахгүй бол render бүрт шинэ функц үүснэ
  // → memo ажиллахгүй болно
  const handlePress = useCallback(() => {
    console.log('Курс сонгогдлоо');
  }, []); // dependency хоосон = функц хэзээ ч өөрчлөгдөхгүй

  return (
    <View style={styles.container}>
      <TouchableOpacity onPress={() => setCount(c => c + 1)}>
        <Text style={styles.counter}>Тоолуур: {count} (дарахад CourseCard re-render болохгүй)</Text>
      </TouchableOpacity>
      <CourseCard title="JavaScript үндэс" onPress={handlePress} />
      <CourseCard title="React Native" onPress={handlePress} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16, backgroundColor: '#0b1120' },
  card: { backgroundColor: '#0f172a', padding: 16, borderRadius: 12, marginBottom: 8 },
  title: { color: '#f1f5f9', fontSize: 16 },
  counter: { color: '#94a3b8', marginBottom: 16, fontSize: 14 },
});

useMemo нь тооцооллын үр дүнг cache хийнэ — нарийн шүүлтүүр, эрэмбэлэлт зэрэгт хэрэгтэй:

typescript
import { useMemo, useState } from 'react';

const lessons = [
  { id: 1, title: 'React Native гэж юу вэ?', completed: true },
  { id: 2, title: 'Node.js суулгах', completed: true },
  { id: 3, title: 'Expo тохируулга', completed: false },
  { id: 4, title: 'Анхны апп', completed: false },
];

export default function LessonList() {
  const [showCompleted, setShowCompleted] = useState(true);

  // showCompleted өөрчлөгдөхөд л дахин шүүнэ, бусад render-д cache ашиглана
  const filtered = useMemo(
    () => lessons.filter(l => showCompleted || !l.completed),
    [showCompleted]
  );

  return (
    // ...filtered-г харуулах UI
  );
}

FlatList оновчлол

FlatList нь том жагсаалтад зориулагдсан боловч буруу ашиглавал удаашрал үүснэ. Доорх props-үүд гүйцэтгэлд мэдэгдэхүйц нөлөөтэй:

typescript
import { FlatList, View, Text, StyleSheet } from 'react-native';
import { memo, useCallback } from 'react';

type Lesson = { id: string; title: string; order: number };

// Item component-г memo-оор бүтээх нь чухал
const LessonItem = memo(({ item }: { item: Lesson }) => (
  <View style={styles.item}>
    <Text style={styles.order}>{item.order}</Text>
    <Text style={styles.title}>{item.title}</Text>
  </View>
));

export default function OptimizedList({ lessons }: { lessons: Lesson[] }) {
  // keyExtractor render бүрт шинэ функц болохоос сэргийлнэ
  const keyExtractor = useCallback((item: Lesson) => item.id, []);

  // renderItem render бүрт шинэ функц болохоос сэргийлнэ
  const renderItem = useCallback(
    ({ item }: { item: Lesson }) => <LessonItem item={item} />,
    []
  );

  return (
    <FlatList
      data={lessons}
      keyExtractor={keyExtractor}
      renderItem={renderItem}
      // Дэлгэцнээс гадна хэдэн item урьдчилан render хийхийг тохируулна
      windowSize={5}
      // Эхний render-д хэдэн item харуулах
      initialNumToRender={10}
      // Scroll хийхэд хэдэн item нэмж render хийх
      maxToRenderPerBatch={5}
      // Item өндрийг мэдэж байвал getItemLayout ашиглах нь маш хурдан
      getItemLayout={(_, index) => ({
        length: 60,
        offset: 60 * index,
        index,
      })}
      removeClippedSubviews={true}
    />
  );
}

const styles = StyleSheet.create({
  item: { flexDirection: 'row', padding: 16, borderBottomWidth: 1, borderBottomColor: '#1e293b', height: 60, alignItems: 'center' },
  order: { color: '#475569', width: 32, fontSize: 14 },
  title: { color: '#f1f5f9', flex: 1, fontSize: 15 },
});

Зураг болон сүлжээний оновчлол

Зурган файлууд нь аппын хамгийн их санах ой зарцуулдаг хэсэг. Хэдэн арга хэмжээ авснаар мэдэгдэхүйц сайжирна:

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

// Муу: том хэмжээтэй зургийг жижиг контейнерт харуулна
function BadExample() {
  return (
    <Image
      source={{ uri: 'https://example.com/huge-4k-image.jpg' }}
      style={{ width: 80, height: 80 }}
    />
  );
}

// Сайн: зөв хэмжээний зураг + cache тохируулга
function GoodExample() {
  return (
    <Image
      source={{ uri: 'https://example.com/thumbnail-80x80.jpg' }}
      style={styles.avatar}
      // Зургийг дахин татахгүйн тулд cache хийнэ
      defaultSource={require('./assets/placeholder.png')}
      fadeDuration={200}
      resizeMode="cover"
    />
  );
}

// Expo Image нь илүү хурдан, cache дэмжсэн сонголт
import { Image as ExpoImage } from 'expo-image';

function BestExample() {
  return (
    <ExpoImage
      source="https://example.com/image.jpg"
      style={styles.avatar}
      placeholder={{ blurhash: 'L6PZfSi_.AyE_3t7t7R**0o#DgR4' }}
      contentFit="cover"
      transition={200}
    />
  );
}

const styles = StyleSheet.create({
  avatar: { width: 80, height: 80, borderRadius: 40 },
});

Гүйцэтгэлийн оновчлол нь нэг удаа хийгдэх зүйл биш — апп өсөх тусам байнга анхаарах хэрэгтэй. Харин энд сурсан суурь аргуудыг эхнээс нь мөрдвөл том асуудал гарахгүй.

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

Hermes engine — Expo болон React Native-д суурилагдсан JavaScript engine-г тохируулах, ашиглах, дибаг хийх талаар сурна. Hermes нь аппын эхлэх хурдыг мэдэгдэхүйц нэмэгдүүлдэг.