المكون FlatList في React Native

من موسوعة حسوب
مراجعة 14:01، 9 أكتوبر 2021 بواسطة جميل-بيلوني (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

واجهة عالية الأداء لتصيير قوائم مسطحة بسيطة تدعم الميزات الأكثر فائدة:

  • عابر منصات (أو متعدّد المنصات: cross-platform) بالكامل.
  • وضع أفقي اختياري.
  • دوال رد نداء قابلة للضبط للتحكم بقابلية العرض (viewability callbacks).
  • دعم الترويسة (Header).
  • دعم التذييل (Footer).
  • دعم الفواصل.
  • التحديث بالسحب.
  • التحميل أثناء التمرير (Scroll loading).
  • دعم التمرير إلى فهرس باستخدام ScrollToIndex.
  • دعم الأعمدة المتعددة.

إذا احتجت لاستعمال الأقسام، فاستخدم <SectionList>.

إليك مقال بسيط (تجربة حية):

import React from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';

const DATA = [
  {
    id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
    title: 'First Item',
  },
  {
    id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
    title: 'Second Item',
  },
  {
    id: '58694a0f-3da1-471f-bd96-145571e29d72',
    title: 'Third Item',
  },
];

const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const App = () => {
  const renderItem = ({ item }) => (
    <Item title={item.title} />
  );

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0,
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 32,
  },
});

export default App;

لتصيير أعمدة متعددة، استخدم خاصية numColumns يمكن أن يؤدي استخدام هذا الأسلوب بدلاً من تخطيط flexWrap إلى منع التعارضات مع منطق ارتفاع العنصر (item height logic).

إليك مثال أعقد:

  • بتمرير ‎‎extraData={selectedId}‎‎ إلى FlatList، نتأكد من إعادة تصيير FlatList نفسه عندما يحدث تغير في الحالة‎‎. إذا لم تُعيَّن هذه الخاصية فلن يعلم مكوّن FlatList أنه يحتاج إلى إعادة تصيير أي عناصر لأنه أيضًا مكوِّنٌ من النوع PureComponent ولن تعرض مقارنة الخاصيات أي تغييرات.
  • يخبر keyExtractor القائمة باستخدام المعرفات (‎‎id‎‎) لمفاتيح react بدلاً من خاصية المفتاح key الافتراضية.

شيفرة المثال (تجربة حية):

import React, { useState } from "react";
import { FlatList, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity } from "react-native";

const DATA = [
  {
    id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
    title: "First Item",
  },
  {
    id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
    title: "Second Item",
  },
  {
    id: "58694a0f-3da1-471f-bd96-145571e29d72",
    title: "Third Item",
  },
];

const Item = ({ item, onPress, backgroundColor, textColor }) => (
  <TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
    <Text style={[styles.title, textColor]}>{item.title}</Text>
  </TouchableOpacity>
);

const App = () => {
  const [selectedId, setSelectedId] = useState(null);

  const renderItem = ({ item }) => {
    const backgroundColor = item.id === selectedId ? "#6e3b6e" : "#f9c2ff";
    const color = item.id === selectedId ? 'white' : 'black';

    return (
      <Item
        item={item}
        onPress={() => setSelectedId(item.id)}
        backgroundColor={{ backgroundColor }}
        textColor={{ color }}
      />
    );
  };

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        extraData={selectedId}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0,
  },
  item: {
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 32,
  },
});

export default App;

هذا مغلِّف ملائم حول <VirtualizedList>، وبالتالي فإنّه يرث خاصياته (بالإضافة إلى خاصيات <ScrollView>) التي لم تُصنَّف بشكل صريح هنا، إضافةً إلى التحذيرات التالية:

  • لا يُحتفظ بالحالة الداخلية عند تمرير (scroll) المحتوى خارج نافذة التصيير. تأكد من تسجيل جميع بياناتك في بيانات العنصر أو المخازن الخارجية مثل Flux أو Redux أو Relay.
  • هذا مكوّنُ PureComponent، ما يعني أنه لن يعاد تصييره إذا ظلت الخاصيات ‎‎props‎‎ متساويّةً سَطحيًّا (shallow-equal). تأكّد من تمرير كل ما تعتمد عليه دالة renderItem كخاصياتٍ (مثل extraData) لا تكون متساويًّةً (‎‎===‎‎) بعد التحديثات، وإلا فقد لا تُحدَّث واجهة مستخدمك عند حدوث التغييرات. يتضمن هذا خاصية data وحالة المكون الأب.
  • لتقييد الذاكرة وتمكين التمرير السلس (smooth scrolling)، يُصيَّر المحتوى على نحو غير متزامن في الخلفيّة. هذا يعني أنه من الممكن التمرير أسرع من معدل التعبئة ورؤية المحتوى الفارغ مؤقتا. هذه مقايضة يمكن تعديلها لتناسب احتياجات كل تطبيق، وسيعمل فريق إطار العمل على تحسين هذا الأمر.
  • افتراضيا، تبحث القائمة عن خاصية key في كل عنصر وتستخدم قيمتها كمفتاح React (أو React key). كبديل، يمكنك توفير خاصية keyExtractor مخصصة.

الخاصيات

يرث هذا المكوّن خاصياتِ المكوّن ScrollView أيضًا، إلا إذا كان متداخلًا في قائمة FlatList أخرى بنفس الاتجاه.

‎‎renderItem‎‎

تأخذ عنصرًا من الخاصيّة data وتُصيِّره في القائمة.

تُوفر بيانات وصفية إضافية مثل index إذا احتجتها، بالإضافة إلى دالة separators.updateProps أكثر عمومية، والتي تتيح لك تعيين الخاصيات التي تريد تغيير تصييرها إما بفاصل سابق (leading separator) أو بفاصل لاحِق (trailing separator)، وهذا في حالة كانت highlight أو unhighlight الأكثر شيوعًا (والتي تعيّن خاصية highlighted: boolean) غيرَ كافيةٍ بالنسبة لك.

النوع مطلوب
دالة نعم
  • item (كائن): العنصر قيد التصيير من عناصر data.
  • index (عدد): الفهرس المقابل لهذا العنصر في مصفوفة data.
  • separators (كائن)
    • highlight (دالة)
    • unhighlight (دالة)
    • updateProps (دالة)
      • ‎select ‎ : ثابت متعدّد enum('leading', 'trailing')
      • newProps (كائن)

مثال:

<FlatList
  ItemSeparatorComponent={
    Platform.OS !== 'android' &&
    (({ highlighted }) => (
      <View
        style={[
          style.separator,
          highlighted && { marginLeft: 0 }
        ]}
      />
    ))
  }
  data={[{ title: 'Title Text', key: 'item1' }]}
  renderItem={({ item, index, separators }) => (
    <TouchableHighlight
      key={item.key}
      onPress={() => this._onPress(item)}
      onShowUnderlay={separators.highlight}
      onHideUnderlay={separators.unhighlight}>
      <View style={{ backgroundColor: 'white' }}>
        <Text>{item.title}</Text>
      </View>
    </TouchableHighlight>
  )}
/>

‎‎data‎‎

لتبسيط الأمور، الخاصيّة ‎‎data‎‎ مجرد مصفوفة عادية. إذا أردت استخدام شيء آخر، مثل قائمة غير قابلة للتغيير (immutable list)، فاستخدم مكوّن VirtualizedList الأساسيّ مباشرةً.

النوع مطلوب
مصفوفة نعم

‎‎ItemSeparatorComponent‎‎

يُصيَّر بين كل عنصر، ولكن ليس في الجزء العلوي أو السفلي. افتراضيًا، تُوفَّر خاصيات highlighted و leadingItem. يوفر renderItem كلا من separators.highlight و separators.unhighlight والذي سيحدِّث خاصية highlighted، ولكن يمكنك أيضًا إضافة خاصيات مخصصةٍ باستخدام separators.updateProps.

النوع مطلوب
مكوّن لا

‎‎ListEmptyComponent‎‎

يُصيَّر عندما تكون القائمة فارغة. يمكن أن يكون مكون React (مثل SomeComponent) أو عنصر React (مثل <SomeComponent /‎>).

النوع مطلوب
مكون، عنصر لا

‎‎ListFooterComponent‎‎

يُصيّر أسفلَ جميع العناصر. يمكن أن يكون مكون React (مثل SomeComponent) أو عنصر React (مثل <SomeComponent /‎>).

النوع مطلوب
مكون، عنصر لا

‎‎ListFooterComponentStyle‎‎

نمط للواجهة View الداخلية لمكّون ListFooterComponent.

النوع مطلوب
كائن نمط (style object) لا

‎‎ListHeaderComponent‎‎

يُصيّر أعلى جميع العناصر. يمكن أن يكون مكون React (مثل SomeComponent) أو عنصر React (مثل <SomeComponent /‎>).

النوع مطلوب
مكون، عنصر لا

‎‎ListHeaderComponentStyle‎‎

نمط العرض الداخلي للمكوّن ListHeaderComponent.

النوع مطلوب
كائن نمط (style objects) لا

‎‎columnWrapperStyle‎‎

نمط مخصص اختياري للصفوف متعددة العناصر المولّدة عند تحّقق الشرط numColumns > 1.

النوع مطلوب
كائن نمط لا

‎‎extraData‎‎

خاصية تعليم (marker property) لإخبار القائمة بإعادة التصيير (نظرًا لأنها تنفّذ PureComponent). إذا كانت أي من دوالك renderItem، و Header، و Footer، وما إلى ذلك، تعتمد على أي شيء خارج خاصية data، فضعها هنا وعالجها بطريقة غير قابلة للتغيير (immutably).

النوع مطلوب
أي نوع any لا

‎‎getItemLayout‎‎

(data, index) => {length: number, offset: number, index: number}

الخاصية getItemLayout هي تحسين اختياري تتيح لنا تخطي قياس المحتوى الديناميكي إذا كنت تعرف أبعاد العناصر في وقت مبكر. تعَدّ getItemLayout طريقة فعالة وسهلة الاستخدام إذا كان لديك عناصر ثابتة الحجم، على سبيل المثال:

  getItemLayout={(data, index) => (
    {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
  )}

يمكن أن تكون إضافة getItemLayout بمثابة تعزيز أداء رائع لقوائم تضم مئات العناصر. تذكر تضمين طول الفاصل (الارتفاع أو العرض) في حساب الإزاحة إذا قمت بتحديد ItemSeparatorComponent.

النوع مطلوب
دالة لا

‎‎horizontal‎‎

في حالة القيمة ‎‎true‎‎، يصيِّر العناصر قرب بعضها البعض أفقيًا بدلاً من تكديسها رأسيًا.

النوع مطلوب
قيمة منطقية لا

‎‎initialNumToRender‎‎

عدد العناصر المراد تصييرها في الدفعة الأولية. هذا يجب أن يكون كافيا لملء الشاشة لا أكثر. لاحظ أن هذه العناصر لن يُلغى تركيبها أبدًا كجزء من التصيير ذو النوافذ (windowed rendering) لتحسين الأداء المتوقع لإجراءات التمرير إلى الأعلى.

النوع مطلوب
عدد لا

‎‎initialScrollIndex‎‎

بدلاً من البدء من الأعلى بالعنصر الأول، ابدأ من العنصر ذو الفهرَس الموافق للخاصيّة initialScrollIndex. يؤدي هذا إلى تعطيل ميّزة "التمرير إلى أعلى" التي تُحافظ دائمًا على تصيير أول عناصر initialNumToRender وتُصيّر فورًا العناصر بدءًا من هذا الفهرس الأولي، يتطلب تنفيذ getItemLayout.

النوع مطلوب
عدد لا

‎‎inverted‎‎

يعكس اتجاه التمرير. يستخدم تحويلات المقدار ‎‎-1‎‎.

النوع مطلوب
قيمة منطقية لا

‎‎keyExtractor‎‎

(item: object, index: number) => string;

يُستخدم لاستخراج مفتاح فريد لعنصر معين في الفهرس المُحدَّد. يُستخدَم المفتاح للتخزين المؤقت وكمفتاح React لتتبع إعادة ترتيب العناصر. يتحقق المستخرج الافتراضي من قيمة item.key، ثم item.id ثم يعود إلى استخدام الفهرس كما يفعل React.

النوع مطلوب
دالة لا

‎‎numColumns‎‎

يمكن تصيير أعمدة متعددة فقط باستعمال ‎‎horizontal={false}‎‎ وسيتعرّج مثل تخطيط flexWrap. يجب أن تكون جميع العناصر بنفس الارتفاع - تخطيطات البناء (masonry layouts) غير مدعومة.

النوع مطلوب
عدد لا

‎‎onEndReached‎‎

(info: {distanceFromEnd: number}) => void

يُستدعى مرة واحدة عندما يكون موضع التمرير ضمن مجال ‎‎onEndReachedThreshold‎‎ للمحتوى المصيّر.

النوع مطلوب
دالة لا

‎‎onEndReachedThreshold‎‎

كم يجب أن تكون الحافة السفلية للقائمة من نهاية المحتوى (بوحدات الطول المرئي للقائمة) لتشغيل دالة رد النداء onEndReached. وبالتالي، ستُطلِق القيمة 0.5 دالة رد النداء onEndReached عندما تكون نهاية المحتوى ضمن نصف الطول المرئي للقائمة.

النوع مطلوب
عدد لا

‎‎onRefresh‎‎

() => void

إذا توفّر، سيُضاف مكوّن RefreshControl قياسيّ لوظيفة "السحب للتحديث (Pull to Refresh)". تأكد من ضبط خاصية refreshing بشكل صحيح كذلك.

النوع مطلوب
دالة لا

‎‎onViewableItemsChanged‎‎

يُستدعَى عندما تتغير قابلية عرض (viewability) الصفوف، كما هو مُحدَّد من طرف الخاصية ‎‎viewabilityConfig‎‎.

النوع مطلوب
دالة
(callback: { changed: array of ViewTokens, viewableItems: array of ViewTokens }) => void
لا

‎‎progressViewOffset‎‎

عيِّن هذه الخاصيّة عند الحاجة إلى الإزاحة ليظهر مؤشر التحميل بشكل صحيح.

النوع مطلوب
عدد لا

‎‎refreshing‎‎

عيّن لها القيمة ‎‎true‎‎ أثناء انتظار بيانات جديدة من تحديث ما.

النوع مطلوب
قيمة منطقية لا

‎‎removeClippedSubviews‎‎

قد يؤدي هذا إلى تحسين أداء التمرير للقوائم الكبيرة. القيمة الافتراضية على منصة Android هي true.

ملاحظة: قد تكون هناك أخطاء (محتوى مفقود) في بعض الحالات. استخدمها على مسؤوليتك.

النوع مطلوب
قيمة منطقية لا

‎‎viewabilityConfig‎‎

انظر ViewabilityHelper.js‎‎‎‎ لمعرفة نوع التدفق flow ولتفاصيل أكثر.

النوع مطلوب
ViewabilityConfig لا

تأخذ الخاصية viewabilityConfig نوع ViewabilityConfig، وهو كائن ذو الخاصيات التالية:

الخاصية النوع
minimumViewTime عدد
viewAreaCoveragePercentThreshold عدد
itemVisiblePercentThreshold عدد
waitForInteraction قيمة منطقية

أحد هذين على الأقل مطلوب: viewAreaCoveragePercentThreshold أو itemVisiblePercentThreshold. يجب القيام بهذا في دالة البناء constructor لتجنب الخطأ التالي (المرجع):

  Error: Changing viewabilityConfig on the fly is not supported
constructor (props) {
  super(props)

  this.viewabilityConfig = {
      waitForInteraction: true,
      viewAreaCoveragePercentThreshold: 95
  }
}
<FlatList
    viewabilityConfig={this.viewabilityConfig}
  ...

‎‎minimumViewTime‎‎

الحد الأدنى من الوقت (بالمللي ثانية) الذي يجب أن يكون العنصر فيه قابلا للعرض فعليًا قبل إطلاق دالة رد نداء قابلية العرض (viewability callback). العدد الكبير يعني أن التمرير خلال المحتوى دون توقف لن يؤدي إلى تمييز المحتوى على أنه قابل للعرض.

‎‎viewAreaCoveragePercentThreshold‎‎

نسبة إطار العرض (viewport) الواجب تغطيتها لعنصر مغلق جزئيًا (partially occluded) ليُعدَّ على أنه "قابل للعرض (viewable)"، من 0 إلى 100. تُعدّ العناصر المرئية بالكامل قابلة للعرض دائمًا. تعني القيمة 0 أن بِكْسِل واحد في منفذ العرض يجعل العنصر قابلاً للعرض، والقيمة 100 تعني أن العنصر إما يجب أن يكون مرئيًا تمامًا أو يغطي منفذ العرض بالكامل ليُعدّ قابلًا للعرض.

‎‎itemVisiblePercentThreshold‎‎

تشبه viewAreaCoveragePercentThreshold، ولكنها تأخذ في الاعتبار نسبة العنصر المرئي، بدلًا من جزءٍ من المساحة القابلة للعرض التي يغطّيها.

‎‎waitForInteraction‎‎

لا يمكن اعتبار أي شيء قابلاً للعرض حتى يمرِّر المستخدم أو عند استدعاء recordInteraction بعد التصيير.

‎‎viewabilityConfigCallbackPairs‎‎

قائمة أزواج ViewabilityConfig / onViewableItemsChanged.

ستُستدعى الدالة onViewableItemsChanged عند استيفاء شروط ViewabilityConfig ذات العلاقة.

انظر ViewabilityHelper.js‎‎‎‎ لمعرفة نوع flow ولتوثيق أكثر.

النوع مطلوب
مصفوفة من ViewabilityConfigCallbackPair لا

التوابع

‎‎flashScrollIndicators()‎‎

flashScrollIndicators();

يعرض مؤشرات التمرير مؤقتا.

‎‎getNativeScrollRef()‎‎

getNativeScrollRef();

يوفر مرجعًا إلى مكون التمرير الأساسي.

‎‎getScrollResponder()‎‎

getScrollResponder();

يوفر مقبضًا (handle) لمستجيب التمرير الأساسي.

‎‎getScrollableNode()‎‎

getScrollableNode();

يوفر مقبضًا لعقدة التمرير الأساسية.

‎‎recordInteraction()‎‎

recordInteraction();

يخبر القائمة بحدوث تفاعل ما، والذي يجب أن يشغّل حسابات قابلية العرض (viewability calculations)، على سبيل المثال، إذا كان waitForInteractions ذا قيمةٍ صحيحة (‎‎true‎‎) ولم يمرِّر المستخدم. يُستدعى هذا عادةً عن طريق النقر على العناصر أو عن طريق إجراءات التنقل.

‎‎scrollToEnd()‎‎

scrollToEnd([params]);

يُمرِّر إلى نهاية المحتوى. قد يعمل بشكل سيّئٍ دون الخاصية getItemLayout.

المعاملات:

الاسم النوع مطلوب الوصف
params كائن لا أنظر أدناه

مفاتيح params الصالحة هي:

  • ‎‎'animated'‎‎ (قيمة منطقيّة): ما إذا وجَب تحريك القائمة أثناء التمرير. ‎‎true‎‎ افتراضيًا.

‎‎scrollToIndex()‎‎

scrollToIndex(params);

تُمرِّر إلى العنصر الموجود في الفهرس المُحدَّد بحيث يوضع في المنطقة القابلة للعرض، بحيث يضعه viewPosition بالقيمة 0 في الأعلى، والقيمة 1 في الأسفل، والقيمة 0.5 في المنتصف.

ملاحظة: لا يمكن التمرير إلى مواقع خارج نافذة التصيير دون تحديد الخاصية getItemLayout.

المعاملات:

الاسم النوع مطلوب الوصف
params كائن نعم أنظر أدناه

مفاتيح params الصالحة هي:

  • ‎‎'animated'‎‎ (قيمة منطقيّة): ما إذا وجَب تحريك القائمة أثناء التمرير. ‎‎true‎‎ افتراضيًا.
  • 'index' (عدد): الفهرس المرغوب التمرير إليه. مطلوب.
  • 'viewOffset' (عدد): عدد ثابت من وحدات البكسل لإزاحة (offset) الموضع الهدف النهائي. مطلوب.
  • 'viewPosition' (عدد): تضع القيمة 0 العنصر المحدَّد بالفهرس في الأعلى، 1 في الأسفل، و0.5 في المنتصف.

‎‎scrollToItem()‎‎

scrollToItem(params);

يُمرِّر القائمة إلى العنصر المحدد، ويتطلب مسح البيانات (data) خطّيًّا، استخدم scrollToIndex بدلاً منه إن أمكن.

ملاحظة: لا يمكن التمرير إلى مواقع خارج نافذة التصيير دون تحديد الخاصية getItemLayout.

المعاملات:

الاسم النوع مطلوب الوصف
params كائن نعم أنظر أدناه

مفاتيح params الصالحة هي:

  • ‎‎'animated'‎‎ (قيمة منطقيّة): ما إذا وجَب تحريك القائمة أثناء التمرير. ‎‎true‎‎ افتراضيًا.
  • 'item' (كائن): العنصر المرغوب التمرير إليه. مطلوب.
  • 'viewPosition' (عدد).

‎‎scrollToOffset()‎‎

scrollToOffset(params);

مرّر إلى محتوى معين في القائمة المزاح بقيمة محددة بالبكسل (content pixel offset).

المعاملات:

الاسم النوع مطلوب الوصف
params كائن نعم أنظر أدناه

مفاتيح params الصالحة هي:

  • ‎‎'offset'‎‎ (عدد): الإزاحة المرغوب التمرير إليها. في حالة كانت قيمةُ horizontal القيمةَ ‎‎true‎‎، فالإزاحة هي القيمة x، وفي أي حالة أخرى، تكون الإزاحة هي القيمة y. مطلوب.
  • ‎‎'animated'‎‎ (قيمة منطقيّة): ما إذا وجَب تحريك القائمة أثناء التمرير.

مصادر