Бесконечная прокрутка позволяет вашему приложению запрашивать / извлекать данные по мере необходимости. Это означает, что когда пользователь прокручивает список до конца, выполняется дополнительный запрос и добавляются данные.
Нет кнопки «Загрузить еще» или «Следующая страница» , запрос выполняется автоматически в фоновом режиме, откуда и происходит название бесконечной прокрутки. Пользователь может прокручивать без усилий и бесконечно (при условии, что данных достаточно, чтобы продолжать бесконечно). Он загружает их, когда они достигают определенной точки на странице, откуда и происходит термин «ленивая загрузка» - он загружает их только тогда, когда вам это нужно.
2. Зачем нужна бесконечная прокрутка
Такие приложения, как Instagram, Facebook, Twitter, YouTube, Pinterest и Medium, используют бесконечную прокрутку, так почему же ее используют крупные технологические компании? Это отличный пользовательский интерфейс, он эффективно запрашивает только необходимые данные и экономит $$$ затрат на передачу данных. В нашем случае мы будем использовать Firebase Cloud Firestore, поэтому бесконечная прокрутка сэкономит затраты на чтение Firebase.
Например, предположим, что у вас есть набор данных из 100 пользователей, но наше приложение может отображать только 9 пользователей одновременно. Было бы неэффективно запрашивать Cloud Firestore для 100 пользователей, если ваш пользователь никогда не прокручивает первые 9 пользователей. Бесконечная прокрутка делает ваше приложение более эффективным, запрашивая по мере необходимости. Это требует минимальных усилий со стороны пользователя, поскольку требуется меньше шагов, поскольку нет номеров страниц и кнопок, которые можно было бы нажимать для запроса дополнительных данных.
Это не только неэффективно с точки зрения затрат на чтение Firebase и пользовательского опыта, но и неэффективно с точки зрения скорости, поскольку оно отправляет вашему приложению только необходимые данные.
3. Пример приложения + код
Репозиторий на Github: https://github.com/jefelewis/« react-native-infinite-scroll-test
A. Обзор приложения
Приложение будет иметь только один экран (InfiniteScroll.js), и оно будет запрашивать из Firebase Cloud Firestore с ограничением в 9 пользователей. Как только вы прокрутите список до конца, будет загружено еще 9 пользователей (всего 20 пользователей).
Все данные будут отображаться в компоненте React Native FlatList.
Б. Скриншот приложения
C. Файловая структура приложения
В этом примере будут использоваться 4 файла:
- data.json (пример данных)
- config.js (конфигурация Firebase)
- App.js (приложение React Native)
- InfiniteScroll.js (экран с бесконечной прокруткой)
D. Файлы приложений
Ниже будет представлен образец файла data.json. Этот data.json нужно будет импортировать в Firebase Cloud Firestore.
Если вы не знаете, как импортировать данные JSON в Firebase Cloud Firestore, проверьте Руководство по импорту JSON в Firestore.
data.json
{ "users": [ { "id": 1, "first_name": "Kristin", "last_name": "Smith" }, { "id": 2, "first_name": "Olivia", "last_name": "Parker" }, { "id": 3, "first_name": "Jimmy", "last_name": "Robinson" }, { "id": 4, "first_name": "Zack", "last_name": "Carter" }, { "id": 5, "first_name": "Brad", "last_name": "Rayburn" }, { "id": 6, "first_name": "Krista", "last_name": "Foster" }, { "id": 7, "first_name": "Parker", "last_name": "Trotter" }, { "id": 8, "first_name": "Kevin", "last_name": "Carter" }, { "id": 9, "first_name": "Fred", "last_name": "Klein" }, { "id": 10, "first_name": "Thomas", "last_name": "Manchin" }, { "id": 11, "first_name": "Taylor", "last_name": "Welch" }, { "id": 12, "first_name": "Sam", "last_name": "Goldberg" }, { "id": 13, "first_name": "John", "last_name": "Russell" }, { "id": 14, "first_name": "Steve", "last_name": "Bell" }, { "id": 15, "first_name": "Kelly", "last_name": "Black" }, { "id": 16, "first_name": "Lena", "last_name": "Hunt" }, { "id": 17, "first_name": "Jessica", "last_name": "Moore" }, { "id": 18, "first_name": "Pete", "last_name": "Wong" }, { "id": 19, "first_name": "Harry", "last_name": "Fordham" }, { "id": 20, "first_name": "Ashley", "last_name": "Blake" } ] }
config.js
// Firebase Config const firebaseConfig = { apiKey: 'API_KEY_HERE', authDomain: 'AUTH_DOMAIN_HERE', databaseURL: 'DATABASE_URL_HERE', projectId: 'PROJECT_ID_HERE', storageBucket: 'STORAGE_BUCKET_HERE', messagingSenderId: 'MESSAGING_ID_HERE', }; // Exports module.exports = firebaseConfig;
App.js
// Imports: Dependencies import React from 'react'; import * as firebase from 'firebase'; import 'firebase/firestore'; import firebaseConfig from './config/config'; // Imports: Screens import InfiniteScroll from './screens/InfiniteScroll'; // Firebase: Initialize firebase.initializeApp({ apiKey: firebaseConfig.apiKey, authDomain: firebaseConfig.authDomain, databaseURL: firebaseConfig.databaseURL, projectId: firebaseConfig.projectId, storageBucket: firebaseConfig.storageBucket, messagingSenderId: firebaseConfig.messagingSenderId, }); // Firebase: Cloud Firestore export const database = firebase.firestore(); // React Native: App export default function App() { return ( <InfiniteScroll /> ); }
InfiniteScroll.js
// Imports: Dependencies import React, { Component } from 'react'; import { ActivityIndicator, Dimensions, FlatList, SafeAreaView, StyleSheet, Text, View } from 'react-native'; import { database } from '../App'; // Screen Dimensions const { height, width } = Dimensions.get('window'); // Screen: Infinite Scroll export default class InfiniteScroll extends React.Component { constructor(props) { super(props); this.state = { documentData: [], limit: 9, lastVisible: null, loading: false, refreshing: false, }; } // Component Did Mount componentDidMount = () => { try { // Cloud Firestore: Initial Query this.retrieveData(); } catch (error) { console.log(error); } }; // Retrieve Data retrieveData = async () => { try { // Set State: Loading this.setState({ loading: true, }); console.log('Retrieving Data'); // Cloud Firestore: Query let initialQuery = await database.collection('users') .where('id', '<=', 20) .orderBy('id') .limit(this.state.limit) // Cloud Firestore: Query Snapshot let documentSnapshots = await initialQuery.get(); // Cloud Firestore: Document Data let documentData = documentSnapshots.docs.map(document => document.data()); // Cloud Firestore: Last Visible Document (Document ID To Start From For Proceeding Queries) let lastVisible = documentData[documentData.length - 1].id; // Set State this.setState({ documentData: documentData, lastVisible: lastVisible, loading: false, }); } catch (error) { console.log(error); } }; // Retrieve More retrieveMore = async () => { try { // Set State: Refreshing this.setState({ refreshing: true, }); console.log('Retrieving additional Data'); // Cloud Firestore: Query (Additional Query) let additionalQuery = await database.collection('users') .where('id', '<=', 20) .orderBy('id') .startAfter(this.state.lastVisible) .limit(this.state.limit) // Cloud Firestore: Query Snapshot let documentSnapshots = await additionalQuery.get(); // Cloud Firestore: Document Data let documentData = documentSnapshots.docs.map(document => document.data()); // Cloud Firestore: Last Visible Document (Document ID To Start From For Proceeding Queries) let lastVisible = documentData[documentData.length - 1].id; // Set State this.setState({ documentData: [...this.state.documentData, ...documentData], lastVisible: lastVisible, refreshing: false, }); } catch (error) { console.log(error); } }; // Render Header renderHeader = () => { try { return ( <Text style={styles.headerText}>Items</Text> ) } catch (error) { console.log(error); } }; // Render Footer renderFooter = () => { try { // Check If Loading if (this.state.loading) { return ( <ActivityIndicator /> ) } else { return null; } } catch (error) { console.log(error); } }; render() { return ( <SafeAreaView style={styles.container}> <FlatList // Data data={this.state.documentData} // Render Items renderItem={({ item }) => ( <View style={styles.itemContainer}> <Text>(ID: {item.id}) {item.first_name} {item.last_name}</Text> </View> )} // Item Key keyExtractor={(item, index) => String(index)} // Header (Title) ListHeaderComponent={this.renderHeader} // Footer (Activity Indicator) ListFooterComponent={this.renderFooter} // On End Reached (Takes a function) onEndReached={this.retrieveMore} // How Close To The End Of List Until Next Data Request Is Made onEndReachedThreshold={0} // Refreshing (Set To True When End Reached) refreshing={this.state.refreshing} /> </SafeAreaView> ) } } // Styles const styles = StyleSheet.create({ container: { height: height, width: width, }, headerText: { fontFamily: 'System', fontSize: 36, fontWeight: '600', color: '#000', marginLeft: 12, marginBottom: 12, }, itemContainer: { height: 80, width: width, borderWidth: .2, borderColor: '#000', justifyContent: 'center', alignItems: 'center', }, text: { fontFamily: 'System', fontSize: 16, fontWeight: '400', color: '#000', }, });
Прокрутка
Вот и все! Теперь вы реализовали бесконечную прокрутку в своем приложении React Native с Firebase Cloud Firestore, что сэкономило вам $ $$ на чтении и улучшило UX.
Никто не идеален. Если вы обнаружили какие-либо ошибки, хотите предложить улучшения или расширить тему, отправьте мне сообщение. Я обязательно добавлю какие-либо улучшения или исправлю любые проблемы.