e
sv

React Native ile bileşen görünürlük sensörü uygulama

avatar

Yazılım Method

  • e 0

    Mutlu

  • e 0

    Eğlenmiş

  • e 0

    Şaşırmış

  • e 0

    Kızgın

  • e 0

    Üzgün

Bu makale, bir React Native uygulamasındaki bileşenler için bir görünürlük sensörünün nasıl uygulanacağı hakkındadır. Dikey veya yatay bir FlatList ve onun onViewableItemsChanged , liste öğeleri görünüm alanında göründüğünde veya kaybolduğunda olayları gözlemlemek mümkündür. Bu konsepti kullanarak, örneğin videoları otomatik olarak oynatmaya başlamak veya pazarlama amacıyla "bileşen tarafından görülen olayları" izlemek için bu olaylara tepki verebilirsiniz.

Bu gönderide, örnek bir proje kullanarak React Native'de bileşen görünürlük sensörünün nasıl uygulanacağını ve aşağıdaki bölümlerde aşamalı olarak çözümün nasıl oluşturulacağını tartışacağız:

"Ara çözümlerin" her biri, çözümün neden eksik olduğuna ilişkin bir açıklama içerir (yani, göreceğiniz gibi, bir React Native oluşturma hatası nedeniyle). İşlerin yürümemesinin farklı nedenlerini açıklamak istedim; Sabit olması gereken FlatList geri çağrımızın sınırlamaları ile bu React memoization Hook'larının ( useCallback , useEffect , vb.) nasıl çalıştığı arasında bir etkileşim vardır.

Örnek projenin kapsamı

Bu makalede sunulan kod örnekleri, GitHub tamamlayıcı projesinin bir parçasıdır. TypeScript kullanan ve create-expo-app ile desteklenen bir Expo projesidir .

Demo kullanım durumu, yalnızca FlatList API'sini kullanmaya odaklanmak için kasıtlı olarak basit tutulmuştur. Star Wars karakterlerini gösterir ve ekranda görünenleri en az iki saniye takip eder. Birden çok kez görünürlerse, yalnızca bir kez izlenirler. Bu nedenle, örnek uygulama, kapsam dışında olacağından, görünürlük olaylarında süslü animasyonlara sahip değildir.

Buna karşılık, FlatList görünürlük değişikliği olaylarını tetiklediğinde çağrılan mantık, işleri basit tutmak için yalnızca bir izleme işlevidir. Bu, yaygın iş uygulamalarında kullanıcı olaylarının izlenmesiyle uyumludur.

Aşağıdaki ekran görüntüsü örnek uygulamayı gösterir.

Görünürlük sensörlü örnek uygulamamız

FlatList bir bakış

Örnek projenin uygulama detaylarına dalmadan önce, bir liste öğesi görünürlük dedektörü uygulamak için önemli FlatList aksesuarlarına bir göz atalım.

Prensip olarak, iki FlatList aksesuarı kullanmanız gerekir:

viewabilityConfig ile, uygulamanız için "görüntülenebilir" ifadesinin ne anlama geldiğini siz belirlersiniz. En sık kullandığım yapılandırma, minimum süre boyunca ( y ms) en az yüzde x görünür olan liste öğelerini algılamaktır.

 görüntülenebilirlikConfig={{
  itemVisiblePercentEşik: 75,
  minimumGörüntüleme Süresi: 2000,
}}

Bu örnek yapılandırmada, liste öğeleri, en az 2 saniye boyunca görünümün en az yüzde 75 içinde olduklarında görünür olarak kabul edilir.

Tür tanımlarıyla birlikte diğer geçerli ayarlar hakkında bilgi edinmek için ViewabilityHelper ViewabilityConfig bölümüne bakın.

viewabilityConfig tipi tanımımızın ayarlarına göre liste öğelerinin görünürlüğü her değiştiğinde çağrılan başka bir FlatList desteğine, onViewableItemsChanged 'a ihtiyacınız var.

 // ...
  <Düz Liste
      veri={listItems}
      renderItem={renderItem}
      onViewableItemsChanged={bilgi => {
        // bilgiye eriş ve görüntülenebilir öğelerle sth yap
      })}
      görüntülenebilirlikConfig={{
        itemVisiblePercentEşik: 100,
        minimumGörüntüleme Süresi: 2000,
      }}
      // ...
  />
  // ...

FlatList tarafından dahili olarak kullanılan VirtualizedList içinde tanımlanan onViewabilityItemsChanged imzasına yakından bakalım. info parametresi aşağıdaki akış tipi tanımına sahiptir:

 // VirtualizedList.js'nin akış tanımı parçası

onViewableItemsChanged?: ?(bilgi: {
    viewableItems: Dizi<ViewToken>,
    değişti: Dizi<ViewToken>,
    ...
  }) => geçersiz

Bir ViewToken nesnesi , bir liste öğesinin görünürlük durumuyla ilgili bilgileri tutar.

 // ViewabilityHelper.js'nin akış tanımı bölümü

ViewToken yazın = {
  öğe: herhangi biri,
  anahtar: dize,
  dizin: ?sayı,
  isViewable: boole,
  bölüm?: herhangi biri,
  ...
};

Bunu nasıl kullanabiliriz? Bir sonraki TypeScript snippet'ine bir göz atalım.

 const onViewableItemsChanged = (bilgi: {viewableItems: ViewToken[]; değişti: ViewToken[] }): void => {      
      const görünürItems = info.changed.filter((giriş) => input.isViewable);
      görünürItems.forEach((görünür) => console.log(visible.item));
  }  

Bu örnekte, yalnızca info.changed ile erişebildiğimiz değiştirilmiş ViewToken s ile info.changed . Burada, viewabilityConfig kriterlerini karşılayan liste öğelerini günlüğe kaydetmek istiyoruz. ViewToken tanımından da görebileceğiniz gibi, gerçek liste öğesi item içinde saklanır.

viewableItems ve changed arasındaki fark nedir?

viewabilityConfig , onViewableItemsChanged tarafından çağrıldıktan sonra, viewabilityConfig , viewableItems ölçütlerimizi karşılayan her liste öğesini depolar. Ancak, changed yalnızca son onViewableItemsChanged çağrısının (yani, son yinelemenin) deltasını tutar.

200 ms için yüzde 100 görünen ve 500 ms için yüzde 75 görünen liste öğeleri için farklı şeyler yapmanız gerekiyorsa, FlatList'in bir dizi ViewabilityConfigCallbackPair nesnesini kabul eden viewabilityConfigCallbackPairs FlatList kullanabilirsiniz.

Bu, VirtualizedList bir parçası olan viewabilityConfigCallbackPairs akış türü tanımıdır.

 // VirtualizedList.js

  görüntülenebilirlikConfigCallbackPairs?: Dizi<GörüntülenebilirlikConfigCallbackPair>

ViewabilityHelper akış türü tanımı, ViewabilityConfigCallbackPair bir parçasıdır.

 // GörüntülenebilirlikHelper.js
dışa aktarma türü ViewabilityConfigCallbackPair = {
  görüntülenebilirlikYapılandırma: GörüntülenebilirlikYapılandırma,
  onViewableItemsChanged: (bilgi: {
    viewableItems: Dizi<ViewToken>,
    değişti: Dizi<ViewToken>,
    ...
  }) => geçersiz,
  ...
};

İşte bir örnek:

 <Düz Liste
    veri={listItems}
    renderItem={renderItem}
    görüntülenebilirlikConfigCallbackPairs={[
      {
        görüntülenebilirlikYapılandırma: {
          minimumGörüntüleme Süresi: 200,
          itemVisiblePercentEşik: 100,
        },
        onViewableItemsChanged: onViewableItemsChanged100
      },
      {
        görüntülenebilirlikYapılandırma: {
          minimumGörüntüleme Süresi: 500,
          itemVisiblePercentEşik: 75
        },
        onViewableItemsChanged: onViewableItemsChanged75
      }
    ]}
    // ...
/>

Liste öğesi algılama algoritmasının uygulama ayrıntılarıyla ilgileniyorsanız, buradan okuyabilirsiniz .

FlatList API'si damıtılmış ( onViewableItemsChanged , viewabilityConfig )

Şu anda ilgili API bölümleri hakkında sahip olduğumuz bilgilerle, bir görünürlük sensörü oluşturmak kolay görünebilir. Ancak, onViewableItemsChanged geçirilen işlevin uygulanması, birkaç tuzaktan geçmeyi içerir.

Bu nedenle, nihai çözüme ulaşana kadar farklı versiyonları örnek olarak çalışacağım. Ara çözümlerin her biri, FlatList API'nin uygulanma şekli ve React'in çalışma şekli nedeniyle hatalara sahiptir.

İki farklı kullanım durumunu ele alacağız. İlki basit bir örnek olacak, ekranda her liste öğesi göründüğünde bir olay tetiklenecek. İkinci ila dördüncü daha karmaşık örnekler, yalnızca bir kez olmak üzere, bir liste öğesi görüntülendiğinde olayların nasıl tetikleneceğini göstermek için birbirinin üzerine kurulur.

Yönetim durumu gerektirdiği için ikinci kullanım durumunu ele alıyoruz ve bu FlatList oluşturma hatasıyla çirkin şeylerin ortaya çıktığı yer burası.

İşte deneyeceğimiz çözümler ve her birinin sorunu:

  1. Ekranda her Star Wars karakteri (yani liste öğesi) göründüğünde izleyin
  2. Durumu tanıtarak her karakteri yalnızca bir kez izleyin
  3. Eski bir kapatma sorununa neden olan useCallback çözümünü düzeltmeye çalışın (bkz.
  4. Önceki duruma erişmek için bir durum güncelleyici işlevi kullanarak sorunu düzeltin (bkz. tamamlayıcı proje dalı ana )

Geçici çözüm 1: Bir liste öğesi ekranda her göründüğünde izleyin

onViewableItemsChanged bu ilk uygulaması, ekranda her göründüğünde görünen her öğeyi izler.

 const trackItem = (öğe: StarWarsCharacter) =>
    console.log("### track " + item.name);
const onViewableItemsChanged =
  (bilgi: {değiştirildi: ViewToken[] }): void => {
    const görünürItems = info.changed.filter((giriş) => input.isViewable);
    görünürItems.forEach((görünür) => {
      trackItem(visible.item);
    });
  };

Fonksiyona iletilen info parametresinin changed nesnesini kullanırız. Yalnızca o anda ekranda görünen liste öğelerini visibleItems değişkeninde saklamak için bu ViewToken dizisi üzerinde yineleniriz. Ardından, liste öğesinin adını konsola yazdırarak bir izleme çağrısını simüle etmek için basitleştirilmiş trackItem işlevimizi çağırırız.

Bu işe yaramalı, değil mi? Ne yazık ki hayır. Render hatası alıyoruz.

İşlev birden çok kez oluşturulduğunda oluşturma hatası

FlatList'in uygulanması, FlatList iletilen onViewableItemsChanged , uygulamanın yaşam döngüsü sırasında yeniden oluşturulmasına izin vermez .

Bunu çözmek için, ilk oluşturulduktan sonra işlevin değişmediğinden emin olmalıyız; render döngüleri sırasında kararlı olması gerekir.

Bunu nasıl yapabiliriz? useCallback Hook'u kullanabiliriz.

 const onViewableItemsChanged = useCallback(
    (bilgi: {değiştirildi: ViewToken[] }): void => {
      const görünürItems = info.changed.filter((giriş) => input.isViewable);
      görünürItems.forEach((görünür) => {
        trackItem(visible.item);
      });
    },
    []
  );

useCallback , işlevimiz not edilir ve değişebilecek hiçbir bağımlılık olmadığından yeniden oluşturulmaz. Oluşturma sorunu ortadan kalkar ve izleme beklendiği gibi çalışır.

Bir karakteri ekranda her göründüklerinde takip edin

Geçici çözüm 2: Bir liste öğesini, durumu tanıtarak yalnızca bir kez izleyin

Ardından, her Star Wars karakterini yalnızca bir kez izlemek istiyoruz. Bu nedenle, kullanıcı tarafından daha önce görülen karakterlerin kaydını tutmak için alreadySeen olan bir React durumu sunabiliriz.

Gördüğünüz gibi, useState Hook bir SeenItem dizisini saklar. onViewableItemsChanged iletilen işlev, zatenSeen olan bir bağımlılığa sahip useCallback alreadySeen . Bunun nedeni, setAlreadySeen iletilen sonraki durumu hesaplamak için bu durum değişkenini kullanmamızdır.

 // TypeScript tanımları
arayüz StarWarsCharacter {
  isim: dize;
  resim: dize;
}
tür SeenItem = {
  [anahtar: dize]: StarWarsCharacter;
};
arayüz ListViewProps {
  karakterler: StarWarsCharacter[];
}
dışa aktarma işlevi ListView({
  karakterler,
}: ListViewProps) {
const [alreadySeen, setAlreadySeen] = useState<SeenItem[]>([]);
const onViewableItemsChanged = useCallback(
    (bilgi: {değiştirildi: ViewToken[] }): void => {
      const görünürItems = info.changed.filter((giriş) => input.isViewable);
      // yan etki gerçekleştir
      görünürItems.forEach((görünür) => {
        const var = zatenSeen.find((önceki) => görünür.item.name önceki);
        if (!varsa) trackItem(visible.item);
      });
      // yeni durumu hesapla
      setAlreadySeen([
        ...çoktan görüldü,
        ...visibleItems.map((görünür) => ({
          [visible.item.name]: görünür.item,
        })),
      ]);
    },
    [çoktan görüldü]
  );
  // JSX'i döndür
}

Yine bir sorunumuz var. alreadySeen bağımlılığı nedeniyle, işlev birden fazla kez oluşturulur ve bu nedenle, oluşturma hatamızdan tekrar memnun oluruz.

Bir ESLint ignore yorumuyla bağımlılığı atlayarak oluşturma hatasından kurtulabiliriz .

 const onViewableItemsChanged = useCallback(
    (bilgi: {değiştirildi: ViewToken[] }): void => {
        // ...
    },
    // hatalı düzeltme
    // eslint-disable-sonraki satır tepki kancaları/kapsamlı-deps
    []
  );

AncakuseEffect Hook ile ilgili yazımda da belirttiğim gibi, Hook'unuzun içinde kullandığınız bağımlılıkları asla atlamamalısınız. ESLint tepki kancaları eklentisinin size bağımlılıkların eksik olduğunu söylemesinin bir nedeni vardır.

Bizim durumumuzda, eski bir kapatma sorunu alıyoruz ve alreadySeen durum değişkenimiz artık güncellenmiyor. Değer, boş bir dizi olan ilk değer olarak kalır.

Dizinin ilk değeri güncellenmiyor

Ancak ESLint eklentisinin yapmamızı söylediğini yaparsak yine can sıkıcı render hatamızı alıyoruz. Bir çıkmazdayız.

FlatList uygulamasının sınırlamaları nedeniyle bir şekilde boş bir bağımlılık dizisiyle bir çözüm bulmamız gerekiyor.

Geçici çözüm 3: Eski kapatma sorununu düzeltmeye ve boş bir bağımlılık dizisine dönmeye çalışın

Boş bir bağımlılık dizisine nasıl geri döneriz? Önceki durumu olan bir fonksiyonu parametre olarak kabul edebilen durum güncelleyici fonksiyonunu kullanabiliriz. useState ve useRef arasındaki farklar hakkındaki LogRocket makalemde durum güncelleyici işlevleri hakkında daha fazla bilgi bulabilirsiniz.

 const onViewableItemsChanged = useCallback(
    (bilgi: {değiştirildi: ViewToken[] }): void => {
      const görünürItems = info.changed.filter((giriş) => input.isViewable);
      setAlreadySeen((prevState: SeenItem[]) => {
        // yan etki gerçekleştir
        görünürItems.forEach((görünür) => {
          const var = prevState.find((önceki) => görünür.item.name önceki);
          if (!varsa) trackItem(visible.item);
        });
        // yeni durumu hesapla
        dönüş [
          ...öncekiDevlet,
          ...visibleItems.map((görünür) => ({
            [visible.item.name]: görünür.item,
          })),
        ];
      });
    },
    []
  );

Aralarındaki temel fark, durum güncelleyici işlevinin önceki duruma erişimi olması ve bu nedenle, alreadySeen durum değişkenine doğrudan erişmemize gerek olmamasıdır. Bu şekilde, boş bir bağımlılık dizimiz olur ve işlev beklendiği gibi çalışır (komşu projede yukarıdaki bölümdeki ekran görüntüsüne bakın).

Bir sonraki bölüm, oluşturma hatası ve eski kapatma sorununu biraz daha tartışıyor.

onViewableItemsChanged ile soruna yakından bakış

useEffect ve useCallback gibi memoization Hook'ları, her bileşen bağlam değişkeni bağımlılık dizisine eklenecek şekilde oluşturulmuştur. Bunun nedeni, Hook'ların yalnızca son çalıştırmaya göre bu bağımlılıklardan en az biri değiştirildiğinde çağrılmasıdır.

Bu zahmetli ve hataya açık görevde size yardımcı olmak için React ekibi bir ESLint eklentisi oluşturdu . Ancak, çalışma zamanında bağımlılığın bir daha asla değişmeyeceğini bilseniz bile, iyi bir geliştirici olarak bunu bağımlılık dizisine eklemeniz gerekir. Eklenti yazarları, örneğin durum güncelleyici işlevlerinin kararlı olduğunu bilirler, bu nedenle eklenti, diğer (saf olmayan) işlevlerin aksine dizide bunu talep etmez.

Özel bir Hook'tan böyle bir durum güncelleyici işlevi döndürürseniz ve bunu bir React bileşeninde kullanırsanız, eklenti yanlışlıkla bunu bağımlılık olarak eklediğini iddia eder. Böyle bir durumda, eklentiyi sessize almak (veya uyarıyla yaşamak) için bir ESLint disable yorumu eklemeniz gerekir.

Ancak bu kötü bir uygulamadır. Bağımlılık dizisine eklemek sorun olmasa da, bu değişken zamanla değişebileceğinden, örneğin yeniden düzenlemeden sonra Hook'un kullanımı daha sağlam hale gelir. Sizin için bir sorun oluşturuyorsa büyük ihtimalle projenizde bir bug vardır.

FlatList gelen bu onViewableItemsChanged prop ile ilgili sorun, bağımlılık dizisine hiçbir şekilde bağımlılık ekleyememenizdir. Bu, FlatList uygulamasının, memoization Hooks kavramlarıyla çelişen bir sınırlamasıdır.

Bu nedenle, bağımlılıktan kurtulmak için bir çözüm bulmalısınız. Büyük olasılıkla, önceki duruma erişmek için bir geri arama işlevini kabul eden bir durum güncelleyici işlevi kullanmak için yukarıda açıkladığım gibi yaklaşımı kullanmak istersiniz.

Karmaşıklığı azaltmak veya özel bir Hook'a yerleştirerek test edilebilirliği artırmak için onViewableItemsChanged işlevinin uygulamasını yeniden düzenlemek istiyorsanız, bir bağımlılığı önlemek mümkün değildir. Şu anda React'e, özel Hook'un yerleşik useRef Hook'un veya durum güncelleyici işlevinin sonucu gibi kararlı bir bağımlılık döndürdüğünü söylemenin bir yolu yoktur.

İşyerindeki mevcut projemde, özel Hook'umdan gelen bağımlılığın kararlı olduğunu bildiğim için bir ESLint disable yorumu eklemeyi seçtim. Ayrıca, özel Kancalar sonucunda elde ettiğiniz saf işlevleri yok sayabilir veya kendileri herhangi bir bağımlılık kullanmadıkları için bunları başka bir dosyadan içe aktarabilirsiniz.

 geri aramayı kullan(
    () => {
      /* 
        Özel bir kancadan veya içe aktarılan saf işlevlerden durum güncelleyici işlevlerini kullanır.
        ESLint eklentisi, fonksiyonların kararlı olduğunu bilmez/bağımlılık kullanmaz. Dizi listesinden atlanabileceklerini bilmiyor.
      /*      
    }, 
    // eslint-disable-sonraki satır tepki kancaları/kapsamlı-deps
    []
)

Özel Hook'ların dönüş değerlerinin kararlı olarak işaretlenmesi konusunda geçmişte pek çok tartışma yapıldı, ancak henüz resmi bir çözüm yok.

Özet

FlatList onViewableItemsChanged API'si, ekranda görünen veya kaybolan bileşenleri algılama yeteneği sağlar. FlatList dikey ve yatay olarak kullanılabilir. Bu nedenle, ekran bileşenleri genellikle bir liste halinde düzenlendiğinden, çoğu kullanım durumunda bu kullanılabilir.

Bu yaklaşımla ilgili olan şey, görünüm alanı algılama mantığının uygulanmasının ilk başta sınırlı, sıkıcı ve hataya açık olmasıdır. Bunun nedeni, atanan işlevin ilk oluşturulduktan sonra asla yeniden yaratılmaması gerektiğidir. Bu, herhangi bir bağımlılığa hiçbir şekilde güvenemeyeceğiniz anlamına gelir! Aksi takdirde render hatası alırsınız.

Özetlemek gerekirse, bu soruna geçici bir çözüm bulmak için seçenekleriniz şunlardır:

  • onViewableItemsChanged atadığınız işlevi, boş bir bağımlılık dizisiyle bir useCallback Hook'a sarın
  • İşlevinizin içinde bir veya daha fazla bileşen durum değişkeni kullanırsanız, durum bağımlılıklarından kurtulmak için önceki duruma erişimi olan bir geri aramayı kabul eden durum güncelleyici işlevini kullanmanız gerekir.
  • Bir durum güncelleyici işlevine veya başka bir dosyadan (içe aktarılan veya özel bir Kanca gibi) salt bir işleve güveniyorsanız, boş bağımlılık dizisini tutabilir ve ESLint eklentisi uyarılarını yok sayabilirsiniz.
  • Başka bağımlılıklara güveniyorsanız (örneğin, prop, bağlam, özel bir Hook'tan durum değişkeni, vb.), bunları kullanmadan bir çözüm bulmanız gerekir.

Görünürlük değişikliği olaylarında karmaşık görevler gerçekleştirmek istiyorsanız, useCallback Hook için boş bir bağımlılık dizisini tutmak için olaylarınızı durum değişkenlerinizi durum güncelleyici işleviyle güncelleyecek şekilde tasarlamanız gerekir. Ardından, bağımlılıklara dayanan mantığı gerçekleştirmek için durum değişikliklerine, örneğin bir useEffect Hook ile yanıt verebilirsiniz. Böyle bir senaryo karmaşık hale gelebilir, ancak burada tartıştığımız geçici çözümlerle, sizin için işe yarayan bir çözümü bulmak ve uygulamak için daha kolay bir zamanınız olur.

LogRocket Blog'da ilk olarak React Native ile bileşen görünürlük sensörünün uygulanması yazısı çıktı.

etiketlerETİKETLER
Üzgünüm, bu içerik için hiç etiket bulunmuyor.

Sıradaki içerik:

React Native ile bileşen görünürlük sensörü uygulama