Menerapkan Gulir Tak Terbatas Dan Pemuatan Gambar Malas Dalam Bereaksi

Diterbitkan: 2022-03-10
Ringkasan cepat Dalam tutorial ini, kita akan mempelajari cara menggunakan HTML Intersection Observer API untuk mengimplementasikan pengguliran tak terbatas dan pemuatan lambat gambar dalam komponen fungsional React. Dalam prosesnya, kita akan belajar bagaimana menggunakan beberapa hook React dan bagaimana membuat Custom Hooks.

Jika Anda telah mencari alternatif untuk pagination, gulir tak terbatas adalah pertimbangan yang baik. Dalam artikel ini, kita akan menjelajahi beberapa kasus penggunaan untuk Intersection Observer API dalam konteks komponen fungsional React. Pembaca harus memiliki pengetahuan tentang komponen fungsional React. Beberapa keakraban dengan kait React akan bermanfaat tetapi tidak diperlukan, karena kita akan melihat beberapa.

Tujuan kami adalah bahwa di akhir artikel ini, kami akan menerapkan gulir tak terbatas dan pemuatan lambat gambar menggunakan API HTML asli. Kita juga akan mempelajari beberapa hal lagi tentang React Hooks. Dengan itu Anda dapat menerapkan gulir tak terbatas dan pemuatan lambat gambar di aplikasi Bereaksi Anda jika perlu.

Mari kita mulai.

Membuat Peta Dengan React Dan Leaflet

Memahami informasi dari file CSV atau JSON tidak hanya rumit, tetapi juga membosankan. Mewakili data yang sama dalam bentuk alat bantu visual lebih sederhana. Shajia Abidi menjelaskan betapa hebatnya alat Leaflet, dan bagaimana banyak jenis peta dapat dibuat. Baca artikel terkait →

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

API Pengamat Persimpangan

Menurut dokumen MDN, “Intersection Observer API menyediakan cara untuk mengamati perubahan secara asinkron di persimpangan elemen target dengan elemen ancestor atau dengan viewport dokumen tingkat atas”.

API ini memungkinkan kami untuk mengimplementasikan fitur-fitur keren seperti scroll tak terbatas dan pemuatan lambat gambar. Pengamat persimpangan dibuat dengan memanggil konstruktornya dan meneruskannya sebagai objek panggilan balik dan opsi. Callback dipanggil setiap kali satu elemen, yang disebut target , berpotongan dengan viewport perangkat atau elemen tertentu, yang disebut root . Kita dapat menentukan root kustom dalam argumen opsi atau menggunakan nilai default.

 let observer = new IntersectionObserver(callback, options);

API mudah digunakan. Contoh tipikal terlihat seperti ini:

 var intObserver = new IntersectionObserver(entries => { entries.forEach(entry => { console.log(entry) console.log(entry.isIntersecting) // returns true if the target intersects the root element }) }, { // default options } ); let target = document.querySelector('#targetId'); intObserver.observe(target); // start observation

entries adalah daftar objek IntersectionObserverEntry . Objek IntersectionObserverEntry menjelaskan perubahan persimpangan untuk satu elemen target yang diamati. Perhatikan bahwa panggilan balik tidak boleh menangani tugas yang memakan waktu karena berjalan di utas utama.

Intersection Observer API saat ini menikmati dukungan browser yang luas, seperti yang ditunjukkan pada caniuse.

Dukungan browser Intersection Observer. (Pratinjau besar)

Anda dapat membaca lebih lanjut tentang API di tautan yang disediakan di bagian sumber daya.

Sekarang mari kita lihat bagaimana menggunakan API ini dalam aplikasi React yang sebenarnya. Versi terakhir dari aplikasi kami akan menjadi halaman gambar yang bergulir tanpa batas dan setiap gambar akan dimuat dengan malas.

Membuat Panggilan API Dengan useEffect

Untuk memulai, klon proyek awal dari URL ini. Ini memiliki pengaturan minimal dan beberapa gaya yang ditentukan. Saya juga telah menambahkan tautan ke CSS Bootstrap di file public/index.html karena saya akan menggunakan kelasnya untuk penataan.

Jangan ragu untuk membuat proyek baru jika Anda mau. Pastikan Anda telah menginstal manajer paket yarn jika Anda ingin mengikuti repo. Anda dapat menemukan petunjuk penginstalan untuk sistem operasi spesifik Anda di sini.

Untuk tutorial ini, kita akan mengambil gambar dari API publik dan menampilkannya di halaman. Kami akan menggunakan API Lorem Picsum.

Untuk tutorial ini, kita akan menggunakan titik akhir, https://picsum.photos/v2/list?page=0&limit=10 , yang mengembalikan array objek gambar. Untuk mendapatkan sepuluh gambar berikutnya, kita ubah nilai halaman menjadi 1, lalu 2, dan seterusnya.

Sekarang kita akan membangun komponen Aplikasi sepotong demi sepotong.

Buka src/App.js dan masukkan kode berikut.

 import React, { useEffect, useReducer } from 'react'; import './index.css'; function App() { const imgReducer = (state, action) => { switch (action.type) { case 'STACK_IMAGES': return { ...state, images: state.images.concat(action.images) } case 'FETCHING_IMAGES': return { ...state, fetching: action.fetching } default: return state; } } const [imgData, imgDispatch] = useReducer(imgReducer,{ images:[], fetching: true}) // next code block goes here }

Pertama, kita mendefinisikan fungsi peredam, imgReducer . Peredam ini menangani dua tindakan.

  1. Tindakan STACK_IMAGES menggabungkan larik images .
  2. Tindakan FETCHING_IMAGES nilai variabel fetching antara true dan false .

Langkah selanjutnya adalah memasang peredam ini ke kait useReducer . Setelah itu selesai, kami mendapatkan kembali dua hal:

  1. imgData , yang berisi dua variabel: images adalah larik objek gambar. fetching adalah boolean yang memberi tahu kita apakah panggilan API sedang berlangsung atau tidak.
  2. imgDispatch , yang merupakan fungsi untuk memperbarui objek peredam.

Anda dapat mempelajari lebih lanjut tentang kait useReducer di dokumentasi React.

Bagian kode selanjutnya adalah tempat kita membuat panggilan API. Tempelkan kode berikut di bawah blok kode sebelumnya di App.js .

 // make API calls useEffect(() => { imgDispatch({ type: 'FETCHING_IMAGES', fetching: true }) fetch('https://picsum.photos/v2/list?page=0&limit=10') .then(data => data.json()) .then(images => { imgDispatch({ type: 'STACK_IMAGES', images }) imgDispatch({ type: 'FETCHING_IMAGES', fetching: false }) }) .catch(e => { // handle error imgDispatch({ type: 'FETCHING_IMAGES', fetching: false }) return e }) }, [ imgDispatch ]) // next code block goes here

Di dalam kait useEffect , kami membuat panggilan ke titik akhir API dengan fetch API. Kami kemudian memperbarui larik gambar dengan hasil panggilan API dengan mengirimkan tindakan STACK_IMAGES . Kami juga mengirimkan tindakan FETCHING_IMAGES setelah panggilan API selesai.

Blok kode berikutnya mendefinisikan nilai kembalian dari fungsi tersebut. Masukkan kode berikut setelah kait useEffect .

 return ( <div className=""> <nav className="navbar bg-light"> <div className="container"> <a className="navbar-brand" href="/#"> <h2>Infinite scroll + image lazy loading</h2> </a> </div> </navv <div id='images' className="container"> <div className="row"> {imgData.images.map((image, index) => { const { author, download_url } = image return ( <div key={index} className="card"> <div className="card-body "> <img alt={author} className="card-img-top" src={download_url} /> </div> <div className="card-footer"> <p className="card-text text-center text-capitalize text-primary">Shot by: {author}</p> </div> </div> ) })} </div> </div> </div> );

Untuk menampilkan gambar, kami memetakan array gambar di objek imgData .

Sekarang mulai aplikasi dan lihat halaman di browser. Anda akan melihat gambar ditampilkan dengan baik dalam kotak responsif.

Bit terakhir adalah mengekspor komponen Aplikasi.

 export default App;
Gambar dalam grid responsif. (Pratinjau besar)

Cabang yang sesuai pada saat ini adalah 01-make-api-calls.

Sekarang mari kita perluas ini dengan menampilkan lebih banyak gambar saat halaman bergulir.

Menerapkan Gulir Tak Terbatas

Kami bertujuan untuk menyajikan lebih banyak gambar saat halaman bergulir. Dari URL titik akhir API, https://picsum.photos/v2/list?page=0&limit=10 , kita tahu bahwa untuk mendapatkan kumpulan foto baru, kita hanya perlu menaikkan nilai page . Kita juga perlu melakukan ini ketika kita kehabisan gambar untuk ditampilkan. Untuk tujuan kita di sini, kita akan tahu bahwa kita telah kehabisan gambar ketika kita mencapai bagian bawah halaman. Saatnya untuk melihat bagaimana Intersection Observer API membantu kita mencapainya.

Buka src/App.js dan buat peredam baru, pageReducer , di bawah imgReducer .

 // App.js const imgReducer = (state, action) => { ... } const pageReducer = (state, action) => { switch (action.type) { case 'ADVANCE_PAGE': return { ...state, page: state.page + 1 } default: return state; } } const [ pager, pagerDispatch ] = useReducer(pageReducer, { page: 0 })

Kami hanya mendefinisikan satu jenis tindakan. Setiap kali tindakan ADVANCE_PAGE dipicu, nilai page bertambah 1.

Perbarui URL di fungsi fetch untuk menerima nomor halaman secara dinamis seperti yang ditunjukkan di bawah ini.

 fetch(`https://picsum.photos/v2/list?page=${pager.page}&limit=10`)

Tambahkan pager.page ke larik dependensi bersama imgData . Melakukan hal ini memastikan bahwa panggilan API akan berjalan setiap kali pager.page berubah.

 useEffect(() => { ... }, [ imgDispatch, pager.page ])

Setelah kait useEffect untuk panggilan API, masukkan kode di bawah ini. Perbarui jalur impor Anda juga.

 // App.js import React, { useEffect, useReducer, useCallback, useRef } from 'react'; useEffect(() => { ... }, [ imgDispatch, pager.page ]) // implement infinite scrolling with intersection observer let bottomBoundaryRef = useRef(null); const scrollObserver = useCallback( node => { new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { pagerDispatch({ type: 'ADVANCE_PAGE' }); } }); }).observe(node); }, [pagerDispatch] ); useEffect(() => { if (bottomBoundaryRef.current) { scrollObserver(bottomBoundaryRef.current); } }, [scrollObserver, bottomBoundaryRef]);

Kami mendefinisikan variabel bottomBoundaryRef dan menetapkan nilainya ke useRef(null) . useRef memungkinkan variabel mempertahankan nilainya di seluruh render komponen, yaitu nilai variabel saat ini tetap ada saat komponen yang memuat dirender ulang. Satu-satunya cara untuk mengubah nilainya adalah dengan menetapkan kembali properti .current pada variabel itu.

Dalam kasus kami, bottomBoundaryRef.current dimulai dengan nilai null . Saat siklus rendering halaman berlanjut, kami menetapkan propertinya saat ini menjadi node <div id='page-bottom-boundary'> .

Kami menggunakan pernyataan penugasan ref={bottomBoundaryRef} untuk memberi tahu React untuk mengatur bottomBoundaryRef.current menjadi div tempat penugasan ini dideklarasikan.

Dengan demikian,

 bottomBoundaryRef.current = null

pada akhir siklus rendering, menjadi:

 bottomBoundaryRef.current = <div></div>

Kita akan melihat di mana tugas ini dilakukan dalam satu menit.

Selanjutnya, kita mendefinisikan fungsi scrollObserver , di mana untuk mengatur pengamat. Fungsi ini menerima simpul DOM untuk diamati. Poin utama yang perlu diperhatikan di sini adalah bahwa setiap kali kita mencapai persimpangan yang diamati, kita mengirimkan tindakan ADVANCE_PAGE . Efeknya adalah menaikkan nilai pager.page sebanyak 1. Setelah ini terjadi, hook useEffect yang memilikinya sebagai dependensi akan dijalankan kembali. Jalankan ulang ini, pada gilirannya, memanggil panggilan pengambilan dengan nomor halaman baru.

Prosesi acara terlihat seperti ini.

Tekan persimpangan di bawah pengamatan → panggil tindakan ADVANCE_PAGE → nilai kenaikan pager.page sebesar 1 → kait useEffect untuk menjalankan panggilan pengambilan → panggilan fetch dijalankan → gambar yang dikembalikan digabungkan ke array images .

Kami memanggil scrollObserver di hook useEffect sehingga fungsi hanya akan berjalan ketika salah satu dependensi hook berubah. Jika kita tidak memanggil fungsi di dalam kait useEffect , fungsi tersebut akan berjalan pada setiap render halaman.

Ingat bahwa bottomBoundaryRef.current merujuk ke <div id="page-bottom-boundary" style="border: 1px solid red;"></div> . Kami memeriksa bahwa nilainya bukan nol sebelum meneruskannya ke scrollObserver . Jika tidak, konstruktor IntersectionObserver akan mengembalikan kesalahan.

Karena kita menggunakan scrollObserver dalam hook useEffect , kita harus membungkusnya dalam hook useCallback untuk mencegah perenderan ulang komponen yang tidak berakhir. Anda dapat mempelajari lebih lanjut tentang useCallback di dokumen React.

Masukkan kode di bawah ini setelah <div id='images'> .

 // App.js <div id='image'> ... </div> {imgData.fetching && ( <div className="text-center bg-secondary m-auto p-3"> <p className="m-0 text-white">Getting images</p> </div> )} <div id='page-bottom-boundary' style={{ border: '1px solid red' }} ref={bottomBoundaryRef}></div>

Saat panggilan API dimulai, kami menyetel fetching ke true , dan teks Mendapatkan gambar menjadi terlihat. Segera setelah selesai, kami menyetel fetching ke false , dan teks akan disembunyikan. Kami juga dapat memicu panggilan API sebelum mencapai batas secara tepat dengan menetapkan threshold yang berbeda di objek opsi konstruktor. Garis merah di bagian akhir memungkinkan kita melihat dengan tepat kapan kita mencapai batas halaman.

Cabang yang sesuai pada titik ini adalah 02-gulungan tak terbatas.

Kami sekarang akan menerapkan pemuatan lambat gambar.

Menerapkan Pemuatan Lambat Gambar

Jika Anda memeriksa tab jaringan saat Anda menggulir ke bawah, Anda akan melihat bahwa segera setelah Anda menekan garis merah (batas bawah), panggilan API terjadi, dan semua gambar mulai dimuat bahkan ketika Anda belum sempat melihatnya. mereka. Ada berbagai alasan mengapa ini mungkin bukan perilaku yang diinginkan. Kami mungkin ingin menyimpan panggilan jaringan sampai pengguna ingin melihat gambar. Dalam kasus seperti itu, kita dapat memilih untuk memuat gambar dengan malas, yaitu, kita tidak akan memuat gambar sampai gambar itu bergulir ke tampilan.

Buka src/App.js . Tepat di bawah fungsi gulir tak terbatas, masukkan kode berikut.

 // App.js // lazy loads images with intersection observer // only swap out the image source if the new url exists const imagesRef = useRef(null); const imgObserver = useCallback(node => { const intObs = new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { const currentImg = en.target; const newImgSrc = currentImg.dataset.src; // only swap out the image source if the new url exists if (!newImgSrc) { console.error('Image source is invalid'); } else { currentImg.src = newImgSrc; } intObs.unobserve(node); // detach the observer when done } }); }) intObs.observe(node); }, []); useEffect(() => { imagesRef.current = document.querySelectorAll('.card-img-top'); if (imagesRef.current) { imagesRef.current.forEach(img => imgObserver(img)); } }, [imgObserver, imagesRef, imgData.images]);

Seperti scrollObserver , kita mendefinisikan sebuah fungsi, imgObserver , yang menerima sebuah node untuk diamati. Saat halaman mencapai persimpangan, seperti yang ditentukan oleh en.intersectionRatio > 0 , kita menukar sumber gambar pada elemen. Perhatikan bahwa pertama-tama kita memeriksa apakah sumber gambar baru ada sebelum melakukan swap. Seperti fungsi scrollObserver , kami membungkus imgObserver dalam kait useCallback untuk mencegah rendering ulang komponen yang tidak berakhir.

Perhatikan juga bahwa kita berhenti mengamati elemen img setelah kita selesai dengan substitusi. Kami melakukan ini dengan metode unobserve .

Di kait useEffect berikut, kami mengambil semua gambar dengan kelas .card-img-top pada halaman dengan document.querySelectorAll . Kemudian kami mengulangi setiap gambar dan menetapkan pengamat di atasnya.

Perhatikan bahwa kami menambahkan imgData.images sebagai ketergantungan dari kait useEffect . Ketika perubahan ini memicu kait useEffect dan pada gilirannya imgObserver dipanggil dengan setiap elemen <img className='card-img-top'> .

Perbarui elemen <img className='card-img-top'/> seperti yang ditunjukkan di bawah ini.

 <img alt={author} data-src={download_url} className="card-img-top" src={'https://picsum.photos/id/870/300/300?grayscale&blur=2'} />

Kami menetapkan sumber default untuk setiap elemen <img className='card-img-top'/> dan menyimpan gambar yang ingin kami tampilkan di properti data-src . Gambar bawaan biasanya memiliki ukuran yang kecil sehingga kita download sesedikit mungkin. Saat elemen <img/> muncul, nilai pada properti data-src menggantikan gambar default.

Pada gambar di bawah, kita melihat gambar mercusuar default masih muncul di beberapa ruang.

Gambar sedang dimuat dengan malas. (Pratinjau besar)

Cabang yang sesuai pada saat ini adalah 03-lazy-loading.

Sekarang mari kita lihat bagaimana kita dapat mengabstraksikan semua fungsi ini sehingga dapat digunakan kembali.

Pengambilan Abstrak, Gulir Tak Terbatas, dan Pemuatan Malas Ke Kait Kustom

Kami telah berhasil menerapkan pengambilan, gulir tak terbatas, dan pemuatan lambat gambar. Kami mungkin memiliki komponen lain dalam aplikasi kami yang membutuhkan fungsionalitas serupa. Dalam hal ini, kita dapat mengabstraksi dan menggunakan kembali fungsi-fungsi ini. Yang harus kita lakukan adalah memindahkannya ke dalam file terpisah dan mengimpornya di tempat yang kita butuhkan. Kami ingin mengubahnya menjadi Kait Kustom.

Dokumentasi React mendefinisikan Custom Hook sebagai fungsi JavaScript yang namanya dimulai dengan "use" dan dapat memanggil hook lain. Dalam kasus kami, kami ingin membuat tiga kait, useFetch , useInfiniteScroll , useLazyLoading .

Buat file di dalam folder src/ . Beri nama customHooks.js dan rekatkan kode di bawah ini di dalamnya.

 // customHooks.js import { useEffect, useCallback, useRef } from 'react'; // make API calls and pass the returned data via dispatch export const useFetch = (data, dispatch) => { useEffect(() => { dispatch({ type: 'FETCHING_IMAGES', fetching: true }); fetch(`https://picsum.photos/v2/list?page=${data.page}&limit=10`) .then(data => data.json()) .then(images => { dispatch({ type: 'STACK_IMAGES', images }); dispatch({ type: 'FETCHING_IMAGES', fetching: false }); }) .catch(e => { dispatch({ type: 'FETCHING_IMAGES', fetching: false }); return e; }) }, [dispatch, data.page]) } // next code block here

Kait useFetch menerima fungsi pengiriman dan objek data. Fungsi pengiriman meneruskan data dari panggilan API ke komponen App , sedangkan objek data memungkinkan kita memperbarui URL titik akhir API.

 // infinite scrolling with intersection observer export const useInfiniteScroll = (scrollRef, dispatch) => { const scrollObserver = useCallback( node => { new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { dispatch({ type: 'ADVANCE_PAGE' }); } }); }).observe(node); }, [dispatch] ); useEffect(() => { if (scrollRef.current) { scrollObserver(scrollRef.current); } }, [scrollObserver, scrollRef]); } // next code block here

Kait useInfiniteScroll menerima scrollRef dan fungsi dispatch . scrollRef membantu kami mengatur pengamat, seperti yang telah dibahas di bagian di mana kami mengimplementasikannya. Fungsi pengiriman memberikan cara untuk memicu tindakan yang memperbarui nomor halaman di URL titik akhir API.

 // lazy load images with intersection observer export const useLazyLoading = (imgSelector, items) => { const imgObserver = useCallback(node => { const intObs = new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { const currentImg = en.target; const newImgSrc = currentImg.dataset.src; // only swap out the image source if the new url exists if (!newImgSrc) { console.error('Image source is invalid'); } else { currentImg.src = newImgSrc; } intObs.unobserve(node); // detach the observer when done } }); }) intObs.observe(node); }, []); const imagesRef = useRef(null); useEffect(() => { imagesRef.current = document.querySelectorAll(imgSelector); if (imagesRef.current) { imagesRef.current.forEach(img => imgObserver(img)); } }, [imgObserver, imagesRef, imgSelector, items]) }

Kait useLazyLoading menerima pemilih dan larik. Selector digunakan untuk mencari gambar. Setiap perubahan dalam larik memicu kait useEffect yang mengatur pengamat pada setiap gambar.

Kita dapat melihat bahwa itu adalah fungsi yang sama yang kita miliki di src/App.js yang telah kita ekstrak ke file baru. Hal baiknya sekarang adalah kita dapat menyampaikan argumen secara dinamis. Sekarang mari kita gunakan kait khusus ini di komponen Aplikasi.

Buka src/App.js . Impor kait khusus dan hapus fungsi yang kami tetapkan untuk mengambil data, gulir tak terbatas, dan pemuatan lambat gambar. Tinggalkan reduksi dan bagian di mana kita menggunakan useReducer . Tempelkan kode di bawah ini.

 // App.js // import custom hooks import { useFetch, useInfiniteScroll, useLazyLoading } from './customHooks' const imgReducer = (state, action) => { ... } // retain this const pageReducer = (state, action) => { ... } // retain this const [pager, pagerDispatch] = useReducer(pageReducer, { page: 0 }) // retain this const [imgData, imgDispatch] = useReducer(imgReducer,{ images:[], fetching: true }) // retain this let bottomBoundaryRef = useRef(null); useFetch(pager, imgDispatch); useLazyLoading('.card-img-top', imgData.images) useInfiniteScroll(bottomBoundaryRef, pagerDispatch); // retain the return block return ( ... )

Kami telah berbicara tentang bottomBoundaryRef di bagian gulir tak terbatas. Kami meneruskan objek pager dan fungsi imgDispatch ke useFetch . useLazyLoading menerima nama kelas .card-img-top . Perhatikan . termasuk dalam nama kelas. Dengan melakukan ini, kita tidak perlu menentukannya document.querySelectorAll . useInfiniteScroll menerima ref dan fungsi pengiriman untuk menambah nilai page .

Cabang yang sesuai pada saat ini adalah 04-custom-hooks.

Kesimpulan

HTML semakin baik dalam menyediakan API yang bagus untuk mengimplementasikan fitur-fitur keren. Dalam posting ini, kita telah melihat betapa mudahnya menggunakan pengamat persimpangan dalam komponen fungsional React. Dalam prosesnya, kami belajar bagaimana menggunakan beberapa hook React dan bagaimana menulis hook kami sendiri.

Sumber daya

  • “Gulir Tak Terbatas + Pemuatan Gambar Malas,” Orji Chidi Matthew, GitHub
  • Tombol “Pengguliran Tak Terbatas, Pagination, atau “Muat Lebih Banyak”? Temuan Kegunaan Dalam eCommerce,” Christian Holst, Smashing Magazine
  • “Lorem Picsum,” David Marby & Nijiko Yonskai
  • “IntersectionObserver Mulai Terlihat,” Surma, Dasar-Dasar Web
  • Bisakah Saya Menggunakan… IntersectionObserver
  • “API Pengamat Persimpangan,” dokumen web MDN
  • “Komponen Dan Alat Peraga,” Bereaksi
  • useCallback ,” Bereaksi
  • useReducer ,” Bereaksi