Infinite Scroll
Social media feed-ийг гүйлгэж байхад өгөгдөл дуусдаггүй — энэ нь infinite scroll буюу хязгааргүй гүйлгэлт юм. Хэрэглэгч жагсаалтын эцэст хүрэх үед апп автоматаар дараагийн хуудсыг серверээс татаж нэмдэг. FlatList-н onEndReached prop ашиглан энэ зан үйлийг хэрэгжүүлнэ.
onEndReached үндэс
FlatList-д onEndReached болон onEndReachedThreshold prop нэмнэ. threshold нь 0–1 хооронд утга авах бөгөөд жагсаалтын хэдэн хувьд хүрэхэд callback дуудагдахыг тодорхойлно. 0.5 гэдэг нь жагсаалтын доод 50%-д хүрэхэд дуудагдана гэсэн үг.
import { useState, useCallback } from "react";
import {
View,
Text,
FlatList,
ActivityIndicator,
StyleSheet,
} from "react-native";
const PAGE_SIZE = 10;
export default function InfiniteList() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const fetchPage = useCallback(
async (pageNum) => {
if (loading || !hasMore) return;
setLoading(true);
try {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${pageNum}&_limit=${PAGE_SIZE}`,
);
const data = await res.json();
if (data.length < PAGE_SIZE) setHasMore(false);
setPosts((prev) => [...prev, ...data]);
setPage(pageNum + 1);
} finally {
setLoading(false);
}
},
[loading, hasMore],
);
// Анхны ачаалалт
useState(() => {
fetchPage(1);
}, []);
const renderFooter = () => {
if (!loading) return null;
return (
<View style={styles.footer}>
<ActivityIndicator size="small" color="#22d3ee" />
</View>
);
};
return (
<FlatList
data={posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.card}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.body} numberOfLines={2}>
{item.body}
</Text>
</View>
)}
onEndReached={() => fetchPage(page)}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
contentContainerStyle={styles.list}
/>
);
}
const styles = StyleSheet.create({
list: {
padding: 16,
backgroundColor: "#0b1120",
},
card: {
backgroundColor: "#0f172a",
borderRadius: 12,
padding: 16,
marginBottom: 12,
borderWidth: 1,
borderColor: "#1e293b",
},
title: {
color: "#f1f5f9",
fontSize: 15,
fontWeight: "600",
marginBottom: 6,
textTransform: "capitalize",
},
body: {
color: "#94a3b8",
fontSize: 13,
lineHeight: 18,
},
footer: {
paddingVertical: 20,
alignItems: "center",
},
});
hasMore flag маш чухал — сервер бүх өгөгдлийг илгээж дууссан бол дахин fetch хийхгүй байхад ашиглана.
useEffect-тэй анхны ачаалалт
Дээрх жишээнд анхны ачаалалтыг буруу аргаар дуудсан. Зөв хэлбэр нь useEffect ашиглах:
import { useState, useEffect, useCallback } from "react";
import {
View,
Text,
FlatList,
ActivityIndicator,
StyleSheet,
} from "react-native";
export default function NewsFeed() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [loadingMore, setLoadingMore] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [initialLoading, setInitialLoading] = useState(true);
const load = useCallback(async (pageNum) => {
if (pageNum === 1) setInitialLoading(true);
else setLoadingMore(true);
try {
const res = await fetch(
`https://jsonplaceholder.typicode.com/comments?_page=${pageNum}&_limit=15`,
);
const data = await res.json();
if (data.length < 15) setHasMore(false);
setItems((prev) => (pageNum === 1 ? data : [...prev, ...data]));
setPage(pageNum + 1);
} finally {
setInitialLoading(false);
setLoadingMore(false);
}
}, []);
useEffect(() => {
load(1);
}, []);
if (initialLoading) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color="#22d3ee" />
<Text style={styles.loadingText}>Ачааллаж байна...</Text>
</View>
);
}
return (
<FlatList
data={items}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.email}>{item.email}</Text>
</View>
)}
onEndReached={() => hasMore && !loadingMore && load(page)}
onEndReachedThreshold={0.4}
ListFooterComponent={() =>
loadingMore ? (
<View style={styles.footer}>
<ActivityIndicator size="small" color="#22d3ee" />
</View>
) : !hasMore ? (
<Text style={styles.endText}>— Бүх мэдэгдэл уншигдсан —</Text>
) : null
}
contentContainerStyle={styles.list}
/>
);
}
const styles = StyleSheet.create({
center: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#0b1120",
gap: 12,
},
loadingText: {
color: "#94a3b8",
fontSize: 14,
},
list: {
padding: 16,
backgroundColor: "#0b1120",
},
item: {
backgroundColor: "#0f172a",
padding: 14,
borderRadius: 10,
marginBottom: 10,
borderWidth: 1,
borderColor: "#1e293b",
},
name: {
color: "#f1f5f9",
fontSize: 14,
fontWeight: "600",
marginBottom: 4,
},
email: {
color: "#22d3ee",
fontSize: 12,
},
footer: {
paddingVertical: 20,
alignItems: "center",
},
endText: {
color: "#475569",
textAlign: "center",
paddingVertical: 20,
fontSize: 13,
},
});
Infinite scroll + pull to refresh хослуулах
Жинхэнэ апп-д хоёуланг нь нэгэн зэрэг ашигладаг:
import { RefreshControl } from "react-native";
// FlatList дотор хоёуланг нэмнэ
<FlatList
data={items}
keyExtractor={(item) => item.id.toString()}
renderItem={renderItem}
// Infinite scroll
onEndReached={() => hasMore && !loadingMore && load(page)}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
// Pull to refresh
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={async () => {
setRefreshing(true);
setHasMore(true);
await load(1);
setRefreshing(false);
}}
tintColor="#22d3ee"
colors={["#22d3ee"]}
/>
}
/>;
Infinite scroll бол орчин үеийн мобайл апп-н стандарт функц. Энэ хэрэгжүүлж чадвал таны апп жинхэнэ мэргэжлийн харагдах болно.
Дараагийн хичээлд:
Маягт ба баталгаажуулалт — TextInput ашиглан нэвтрэх маягт, бүртгэлийн маягт хэрхэн зөв хийх талаар сурна.