اسکرول بینهایت یک تکنیک قدرتمند است که تجربه کاربری را با بارگذاری داینامیک محتوا بهبود میبخشد. در این مقاله، سه روش مختلف برای پیادهسازی اسکرول بینهایت در React را بررسی خواهیم کرد که شامل راهکارهای سفارشی و مبتنی بر کتابخانههای آماده میشود.
رایجترین روش برای پیادهسازی اسکرول بینهایت، استفاده از کتابخانههای آماده React مانند
react-infinite-scroll-component
و react-window-infinite-loader
است:
import InfiniteScroll from "react-infinite-scroll-component"; import { useState, useEffect } from 'react'; function App() { const [page, setPage] = useState(1); const [products, setProducts] = useState([]); const [totalProducts, setTotalProducts] = useState(0); const fetchData = async (page: number) => { try { const res = await fetch( `https://dummyjson.com/products/?limit=10&skip=${(page - 1) * 10}` ); const data = await res.json(); if (res.ok) { setProducts((prevItems) => [...prevItems, ...data.products]); page === 1 && setTotalProducts(() => data.total); } } catch (error) { console.log(error) } }; useEffect(() => { let subscribed = true; (async () => { if (subscribed) { await fetchData(1); } })(); return () => { subscribed = false; }; }, []); const handleLoadMoreData = () => { setPage((prevPage) => { const nextPage = prevPage + 1; fetchData(nextPage); return nextPage; }); }; return ( <InfiniteScroll dataLength={products.length} next={handleLoadMoreData} hasMore={totalProducts > products.length} loader={<p>Loading...</p>} endMessage={<p>No more data to load.</p>} > <div> {products.map((product) => ( <div key={product.id}> <h2> {product.title} - {product.id} </h2> </div> ))} </div> </InfiniteScroll> ); }
با این حال، روشهای مختلفی برای ایجاد این افکت وجود دارد. در این مقاله، سه رویکرد منحصربهفرد برای پیادهسازی اسکرول بینهایت در اپلیکیشنهای React را بررسی خواهیم کرد:
react-infinite-scroll-component
و react-window-infinite-loader
که در زمان و انرژی صرفهجویی میکنند و همچنان امکان سفارشیسازی را ارائه میدهند.اسکرول بینهایت نیاز به صفحهبندی سنتی را از بین میبرد. به جای اینکه کاربران بین صفحات مختلف جابهجا شوند، میتوانند بدون توقف اسکرول کنند و محتوای بیشتری ببینند، که این کار تجربهای جذابتر و شهودیتر را فراهم میکند.
این تکنیک به طور گسترده در پلتفرمهای شبکه اجتماعی مانند اینستاگرام، X و تیکتاک استفاده میشود تا کاربران بتوانند بدون وقفه فیدهای پر از تصاویر و ویدیوها را مرور کنند.
اکنون که با مفهوم اسکرول بینهایت آشنا شدیم، وارد بخش آموزش میشویم.
در ابتدا، یک پایه مشترک را ایجاد میکنیم که در تمام روشهای مختلف پیادهسازی اسکرول بینهایت استفاده خواهد شد.
برای شروع، ابتدا یک اپلیکیشن React را با استفاده از Vite راهاندازی میکنیم. برای این کار، دستورات زیر را در ترمینال اجرا مینماییم:
npm create vite@latest ecommerce-app -- --template react-ts cd ecommerce-app npm i
دستورات بالا یک اپلیکیشن React با تایپ اسکریپت ایجاد کرده و تمام وابستگیهای لازم را نصب میکنند. در مرحله بعد، در فایل
App.tsx
state اولیه کامپوننت را تنظیم میکنیم. این شامل لیست آیتمهایی که باید نمایش داده شوند، نشانگرهای بارگذاری و خطا، و یک متغیر برای پیگیری تعداد کل محصولات موجود خواهد بود.
// App.tsx import React, { useState, useEffect } from 'react'; type ProductItem = { id: number; title: string; description: string; category: string; price: number; rating: number; thumbnail: string; brand: string; discountPercentage: number; }; function App() { const [loading, setLoading] = useState(true); const [products, setProducts] = useState<ProductItem[]>([]); const [totalProducts, setTotalProducts] = useState(0); const [error, setError] = useState<null|Error>(null); // rest of component }
در مرحله بعد، یک تابع برای دریافت دادهها از یک API ایجاد میکنیم، شماره صفحه را افزایش میدهیم و state را با آیتمهای دریافتشده بهروزرسانی میکنیم. علاوه بر این، هنگام دریافت دادهها، هرگونه خطا را نیز مدیریت خواهیم کرد:
const fetchData = async (page: number) => { try { setLoading(true); const res = await fetch( `https://dummyjson.com/products/?limit=10&skip=${(page - 1) * 10}` ); const data = await res.json(); if (res.ok) { setProducts((prevItems) => [...prevItems, ...data.products]); page === 1 && setTotalProducts(() => data.total); // only set this once } setLoading(false); } catch (error) { setLoading(false); if (error instanceof Error) { setError(error); } } };
در این مقاله، از API محصولات DummyJSON استفاده خواهیم کرد. این API پارامتر
page
صریحی برای صفحهبندی ندارد. در عوض، از دو پارامتر limit
و skip
برای صفحهبندی استفاده میکند.
limit
حداکثر تعداد محصولاتی است که در هر درخواست API دریافت میکنیم.skip
تعداد آیتمهایی است که قصد داریم در هر صفحه از آنها بگذریم. در اینجا مقدار skip برابر با شماره صفحه قبلی ضربدر ۱۰ خواهد بود.سپس از هوک useEffect
برای فراخوانی تابع
fetchData
در زمان بارگذاری اولیه کامپوننت استفاده خواهیم کرد:
useEffect(() => { let subscribed = true; (async () => { if (subscribed) { await fetchData(1); } })(); return () => { subscribed = false; }; }, []);
درون
useEffect
، باید مطمئن شویم که درخواستهای asynchronous را پاکسازی میکنیم تا از بهروزرسانی state پس از آن که کامپوننت از بین رفت، جلوگیری شود. یکی از روشهای لغو درخواست fetch، استفاده از AbortController
است.
ProductCard
در نهایت، کامپوننت
ProductCard
را ایجاد میکنیم که برای نمایش هر محصول استفاده خواهد شد و استایلهای CSS مربوط به آن را نیز تعریف میکنیم. برای این کار، یک پوشه component
درون مسیر src
ایجاد کرده و فایل ProductCard.tsx
را درون آن میسازیم. سپس کد زیر را در آن قرار میدهیم:
export const ProductCard = ({ product }: { product: ProductItem }) => { const discountedPrice = ( product.price - (product.price * product.discountPercentage) / 100 ).toFixed(2); return ( <div className="product-card"> <img src={product.thumbnail} alt={product.title} className="product-image" /> <div className="product-info"> <h2 className="product-title"> {product.title} - {product.id} </h2> <span className="product-category">{product.category}</span> {product.brand && ( <span className="product-brand">{product.brand}</span> )} <p className="product-description">{product.description}</p> <div className="product-props"> <div className="product-price"> ${discountedPrice} <span className="product-original-price"> ${product.price.toFixed(2)} </span> </div> <div className="product-rating"> <span className="star-rating">{"★"}</span> <span>{Math.floor(product.rating)}</span> </div> </div> <button className="add-to-cart">Add to Cart</button> </div> </div> ); };
قالب Vite که برای ساخت اپلیکیشن React استفاده کردیم، بهطور پیشفرض دو فایل CSS دارد، اما ما فقط به یکی از آنها نیاز داریم.
App.css
را حذف کرده و import آن را از App.tsx
پاک میکنیم.index.css
، استایلهای پیشفرض را با کد زیر جایگزین مینماییم.@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"); .App { font-family: "Poppins", sans-serif; } .products-list { display: grid; grid-template-columns: 1fr; grid-gap: 10px; max-width: 768px; margin: 0 auto; } @media screen and (min-width: 768px) { .products-list { grid-template-columns: 1fr 1fr; } } .product-card { background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); max-width: 400px; width: 100%; overflow: hidden; font-family: "Poppins", sans-serif; } .product-image { width: 100%; height: 250px; object-fit: cover; } .product-info { padding: 20px; } .product-title { font-size: 24px; margin: 0 0 10px; } .product-category, .product-brand { display: inline-block; background-color: #e0e0e0; padding: 5px 10px; border-radius: 15px; font-size: 12px; margin-right: 5px; } .product-description { font-size: 14px; color: #666; margin: 10px 0; } .product-props { display: flex; justify-content: space-between; align-items: center; } .product-price { font-size: 24px; font-weight: bold; margin: 10px 0; } .product-original-price { text-decoration: line-through; color: #999; font-size: 16px; margin-left: 10px; } .product-rating { display: flex; align-items: center; margin: 10px 0; } .star-rating { color: #ffd700; font-size: 18px; margin-right: 5px; } .add-to-cart { display: block; width: 100%; padding: 10px; background-color: #4caf50; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; margin-top: 20px; } .add-to-cart:hover { background-color: #45a049; }
این مراحل پایه در تمام روشهایی که در این مقاله بررسی خواهیم کرد، وجود دارند. ما آنها را اصلاح کرده و به عنوان پایهای برای پیادهسازیهای مختلف توسعه خواهیم داد.
در این روش، ما کل مکانیزم اسکرول بینهایت را از ابتدا میسازیم. این روش شامل کنترل event
scroll
، بارگذاری دادههای بیشتر و بهروزرسانی state در برنامه React است. این روش امکان کنترل کامل بر سفارشیسازی و عملکرد را به ما میدهد.
برای شروع، یک کامپوننت به نام
FromScratch.tsx
در دایرکتوری components ایجاد کرده و مقدار اولیه آن را تنظیم میکنیم.
import { useEffect, useState } from "react"; import { ProductCard } from "./ProductCard"; import { ProductItem } from "../types"; export const FromScratch = ({ products, fetchData, loading, error }: { products: ProductItem[]; fetchData: (page: number) => Promise; loading: boolean; error: null|Error }) => { const [page, setPage] = useState(1); // scroll logic return ( <div> <div className="products-list"> {products.map((product, index) => ( <ProductCard product={product} key={index} /> ))} </div> {loading && <p>Loading...</p>} {error && <p>Error: {error.message}</p>} </div> ); };
scroll
سپس، یک تابع برای مدیریت event
scroll
میسازیم. این تابع بررسی میکند که آیا کاربر به انتهای صفحه رسیده است یا نه؛ و در صورت نیاز، تابع fetchData
را برای دریافت دادههای بیشتر فراخوانی میکند. همچنین، یک event listener برای event scroll
روی window
اضافه میکنیم و هنگام unmount شدن کامپوننت، آن را حذف مینماییم. در کدی که داشتیم، به جای کامنت // scroll logic
کد زیر را اضافه میکنیم:
const handleScroll = () => { const bottom = Math.ceil(window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight - 200; if (bottom) { setPage((prevPage) => { const nextPage = prevPage + 1; fetchData(nextPage); return nextPage; }); } }; useEffect(() => { window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, []);
fetchData
در setPage
سوالی که ممکن است در این مرحله پیش بیاید این است که چرا
fetchData
در داخل callback setPage
قرار گرفته است. دلیل این اتفاق این است که fetchData
به مقدار بهروز state صفحه نیاز دارد.
بهروزرسانی state در React بهصورت asynchronous انجام میشود، بنابراین اگر بلافاصله بعد از setState به مقدار جدید state وابسته باشیم، ممکن است دچار race condition شویم (race condition زمانی رخ میدهد که دو فرآیند سعی میکنند بهطور همزمان به یک منبع دسترسی پیدا کنند). راه دیگر این است که از هوک
useEffect
برای دریافت دادهها هنگام تغییر page
استفاده کنیم، اما در این مورد، این امکان را نداریم.
اکنون، کامپوننتی که ساختیم را در
App.tsx
ایمپورت کرده و اپلیکیشن را اجرا میکنیم:
// App.tsx <div> <FromScratch products={products} fetchData={fetchData} loading={loading} error={error} /> </div>
برای اجرای برنامه میتوانیم از دستور زیر استفاده کنیم:
npm run dev
پس از اجرای برنامه، میتوانیم خروجی آن را در مرورگر مشاهده کنیم. اگر پروژه را همزمان با این آموزش ایجاد نمیکنید، میتوانید از نمونه کد Codesandbox استفاده نمایید.
تابع
handleScroll
در هر بار اسکرول کاربر، بررسی میکند که آیا به انتهای صفحه رسیده یا نه، اما این کار بهینه نیست. در عوض، بهتر است فقط زمانی که کاربر اسکرول را متوقف کرد، بررسی انجام شود. برای این کار، از Debouncing استفاده میکنیم که یک وقفه ایجاد میکند و فقط بعد از متوقف شدن اسکرول، تابع بررسی اجرا میشود:
// FromScratch.tsx const debounce = (func: (args: any) => void, delay: number) => { let timeoutId: ReturnType<typeof setTimeout>; return function (...args: any) { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { func(args); }, delay); }; };
سپس، تابع
handleScroll
را بهروزرسانی میکنیم تا از یک تابع debounce
استفاده کند.
const handleScroll = debounce(() => { const bottom = Math.ceil(window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight - 200; if (bottom) { setPage((prevPage) => { const nextPage = prevPage + 1; fetchData(nextPage); return nextPage; }); } }, ۳۰۰);
هدف از پاک کردن timeout درون تابع debounce این است که هر بار که کاربر دوباره اسکرول را شروع کند، تایمر قبلی لغو شود و تابع بررسی اسکرول بیدلیل اجرا نشود.
در اپلیکیشنهای بزرگ، معمولاً اسکرول بینهایت را در صفحات مختلف نیاز خواهیم داشت. بنابراین، در این بخش، تابع اسکرول را بهصورت یک هوک سفارشی با قابلیت استفاده مجدد پیادهسازی میکنیم.
برای این کار:
src
، یک دایرکتوری جدید به نام hooks
میسازیم.useInfiniteScroll.ts
ایجاد کرده و منطق مربوط به اسکرول را به آن منتقل میکنیم:import { useEffect, useState } from "react"; const debounce = (func: (args: any) => void, delay: number) => { let timeoutId: ReturnType<typeof setTimeout>; return function (...args: any) { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { func(args); }, delay); }; }; export const useInfiniteScroll = (fetchData: (page: number) => Promise<void>) => { const [page, setPage] = useState(1); const handleScroll = debounce(() => { const bottom = Math.ceil(window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight - 200; if (bottom) { setPage((prevPage) => { const nextPage = prevPage + 1; fetchData(nextPage); return nextPage; }); } }, ۳۰۰); useEffect(() => { window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, []); };
حال در
FromScratch.tsx
، منطق اسکرول را با این هوک جدید جایگزین میکنیم.
export const FromScratch = ({ products, fetchData, loading, error, }: { products: ProductItem[]; fetchData: (page: number) => Promise<void>; loading: boolean; error: null | Error; }) => { useInfiniteScroll(fetchData); // rest of component };
با این روش، یک پیادهسازی کاملاً سفارشی و قابل توسعه برای اسکرول بینهایت در React داریم. این روش امکان کنترل کامل و سفارشیسازی بالا را فراهم میکند، اما در عین حال، زمانبرتر است و نسبت به استفاده از کتابخانههای آماده، به نگهداری بیشتری نیاز دارد.
استفاده از یک کتابخانه آماده برای اسکرول بینهایت باعث صرفهجویی در زمان و انرژی میشود، زیرا از راهکارهای آماده و تست شده بهره میبریم و در عین حال امکان سفارشیسازی را نیز خواهیم داشت. در این بخش، دو مورد از این کتابخانهها را بررسی میکنیم.
یکی از محبوبترین کتابخانهها برای پیادهسازی اسکرول بینهایت در React، کتابخانه react-infinite-scroll-component است. در اینجا یاد میگیریم که چگونه از این کتابخانه برای ایجاد اسکرول بینهایت در یک فروشگاه اینترنتی استفاده کنیم.
ابتدا react-infinite-scroll-component را نصب مینماییم:
npm install react-infinite-scroll-component
اکنون، یک کامپوننت جدید در دایرکتوری
components
ایجاد میکنیم و آن را WithReactScroll.tsx
مینامیم. در این کامپوننت:
InfiniteScroll
را از کتابخانه react-infinite-scroll-component ایمپورت میکنیم.dataLength
، next
، hasMore
و loader
، رفتار اسکرول را تنظیم میکنیم.import InfiniteScroll from "react-infinite-scroll-component"; import { ProductCard } from "./ProductCard"; import { useState } from "react"; export const WithReactScroll = ({ products, fetchData, totalProducts, }: { products: ProductItem[]; fetchData: (page: number) => Promise<void>; totalProducts: number; }) => { const [page, setPage] = useState(1); const handleLoadMoreData = () => { setPage((prevPage) => { const nextPage = prevPage + 1; fetchData(nextPage); return nextPage; }); }; return ( <InfiniteScroll dataLength={products.length} next={handleLoadMoreData} hasMore={totalProducts > products.length} loader={<p>Loading...</p>} endMessage={<p>No more data to load.</p>} > <div className="products-list"> {products.map((item) => ( <ProductCard product={item} key={item.id} /> ))} </div> </InfiniteScroll> ); };
اکنون میتوانیم کامپوننت
WithReactScroll
را در App.tsx
ایمپورت کرده و نتیجه را مشاهده کنیم. همچنین میتوانید نتیجه را در Codesandbox نیز ببینیم.
<div> <WithReactScroll products={products} fetchData={fetchData} totalProducts={totalProducts} /> </div>
با این روش، توانستیم اسکرول بینهایت را در اپلیکیشن خود پیادهسازی کنیم، بدون اینکه نیاز باشد event
scroll
ویندوز را بهصورت دستی مدیریت نماییم. کتابخانه react-infinite-scroll-component
این کار را به طور خودکار برای ما انجام میدهد.
مزایا:
معایب:
دومین روش استفاده از کتابخانه react-window است که برای رندر کردن لیستهای بزرگ بهینه شده است. همراه با این کتابخانه، از react-window-infinite-loader برای بارگذاری دادههای بیشتر هنگام اسکرول استفاده میکنیم.
ابتدا react-window و react-window-infinite-loader را نصب مینماییم:
npm install react-window-infinite-loader react-window
سپس، یک کامپوننت جدید در دایرکتوری
components
با نام WithReactWindow
ایجاد میکنیم.
import { useState } from "react"; import { FixedSizeList as List } from "react-window"; import InfiniteLoader from "react-window-infinite-loader"; import { ProductCard } from "./ProductCard"; export const WithReactWindow = ({ fetchData, products, totalProducts, loading, }: { products: ProductItem[]; fetchData: (page: number) => Promise<void>; totalProducts: number; loading: boolean; }) => { const [page, setPage] = useState(1); const hasNextPage = totalProducts > products.length; const handleLoadMoreData = () => { if (loading) return; setPage((prevPage) => { const nextPage = prevPage + 1; fetchData(nextPage); return nextPage; }); }; const isItemLoaded = (index: number) => !hasNextPage || index < products.length; const Row = ({ index, style }: { index: number, style: { [key:string]:any } }) => { return ( <div style={style}> {isItemLoaded(index) ? ( <ProductCard product={products[index]} /> ) : ( "Loading..." )} </div> ); }; return ( <InfiniteLoader isItemLoaded={isItemLoaded} itemCount={hasNextPage ? products.length + 1 : products.length} loadMoreItems={handleLoadMoreData} > {({ onItemsRendered, ref }) => ( <List height={window.innerHeight} itemCount={products.length} itemSize={600} onItemsRendered={onItemsRendered} ref={ref} width={450} > {Row} </List> )} </InfiniteLoader> ); };
در کد بالا، از ترکیب
InfiniteLoader()
و FixedSizeList()
استفاده میکنیم. این ترکیب باعث میشود فقط آیتمهای قابل مشاهده در صفحه رندر شوند. هنگام اسکرول، دادههای جدید بارگذاری میشوند و ویژگی Infinite Scroll را فراهم میکند.
اکنون میتوانیم این کامپوننت را در
App
ایمپورت کنیم تا نتیجه را مشاهده نماییم. همچنین میتوانیم نتیجه را در Codesandbox ببینیم.
<div> <WithReactWindow products={products} fetchData={fetchData} totalProducts={totalProducts} loading={loading} /> </div>
Intersection Observer API یک تکنیک مدرن توسعه است که میتواند تشخیص دهد چه زمانی المنتها به نمایش در میآیند و در نتیجه باعث بارگذاری محتوا برای اسکرول بینهایت شود. این API تغییرات در تقاطع المنت هدف با یک المنت parent یا viewport را مشاهده میکند و آن را برای پیادهسازی اسکرول بینهایت ایدهآل میسازد.
ما یک کامپوننت جدید در دایرکتوری
components
ایجاد میکنیم و آن را WithIntersectionObserver
مینامیم. سپس، یک ref
برای المنت هدف observer
ایجاد کرده و Intersection Observer را در یک هوک از نوع useEffect
تنظیم میکنیم. زمانی که المنت هدف به نمایش درآید، تابع fetchData
را فراخوانی خواهیم کرد.
import { useEffect, useRef, useState } from "react"; import { ProductCard } from "./ProductCard"; export const WithIntersectionObserver = ({ products, fetchData, error, loading }: { products: ProductItem[]; fetchData: (page: number) => Promise<void>; error: null|Error, loading: boolean }) => { const [page, setPage] = useState(1); const observerTarget = useRef(null); useEffect(() => { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { setPage((prevPage) => { const nextPage = prevPage + 1; fetchData(nextPage); return nextPage; }); } }, { threshold: 1 } ); if (observerTarget.current) { observer.observe(observerTarget.current); } return () => { if (observerTarget.current) { observer.unobserve(observerTarget.current); } }; }, [observerTarget]); // rest of component };
سپس، آیتمها، نشانگر بارگذاری، پیامهای خطا و المنت هدف
observer
را درون کامپوننت رندر میکنیم:
return ( <> <div className="products-list"> {products.map((product) => ( <ProductCard product={product} key={product.id} /> ))} </div> <div ref={observerTarget}></div> {loading && <p>Loading...</p>} {error && <p>Error: {error.message}</p>} </> );
با استفاده از Intersection Observer API، ما یک راهحل اسکرول بینهایت کارآمد و با عملکرد بهینه در برنامه React خود ایجاد کردهایم. این روش یک روش مدرن و browser-native برای تشخیص نمایش المنتها است، اما ممکن است در مرورگرهای قدیمی بدون استفاده از polyfill پشتیبانی نشود.
سپس، این کامپوننت جدید را در کامپوننت
App
ایمپورت میکنیم تا نتیجه را ببینیم. همچنین میتوانیم نتیجه را در Codesandbox نیز مشاهده نماییم.
//App.tsx <div> <WithIntersectionObserver products={products} fetchData={fetchData} loading={loading} error={error} /> </div>
ما سه روش مختلف برای پیادهسازی اسکرول بینهایت در React را بررسی کردیم. از آنجایی که احتمالاً فقط به یکی از این روشها در پروژه خود نیاز داریم، انتخاب روش مناسب بستگی به نیازهای خاص برنامه ما دارد. در اینجا برخی نکات کلیدی برای کمک به تصمیمگیری را باهم بررسی میکنیم:
این روش کنترل کامل بر پیادهسازی را فراهم میکند و امکان سفارشیسازی نامحدود را میدهد. با این حال، برای بهینهسازی عملکرد (مثلاً کاهش دفعات اجرای event
scroll
، بهروزرسانی کارآمد DOM) نیاز به تلاش بیشتری دارد و در صورت پیادهسازی نادرست ممکن است دارای اشکالاتی باشد.
چه زمانی از این روش استفاده کنیم؟
کتابخانهها اغلب با بهینهسازیهای داخلی و پشتیبانی community، راهحلی سریع و مطمئن برای پیادهسازی اسکرول بینهایت ارائه میدهند. با این حال، ممکن است باعث افزایش حجم کد برنامه ما شوند و برای بهروزرسانیها و رفع اشکالات به نگهداریکنندگان کتابخانه وابسته خواهیم بود.
چه زمانی از این روش استفاده کنیم؟
این روش مدرن نیازی به استفاده از eventهای
scroll
و کاهش فراخوانی آنها ندارد، بنابراین کارایی بیشتری دارد و نگهداری آن آسانتر است. با این حال، در مرورگرهای قدیمی مانند Internet Explorer پشتیبانی نمیشود.
چه زمانی از این روش استفاده کنیم؟
اسکرول به بالا یک قابلیت اضافی است که معمولاً در اسکرول بینهایت پیادهسازی میشود و تجربه کاربری را بهبود میبخشد.
مثلاً در X، هنگامی که در صفحه For You اسکرول میکنیم، این صفحه هیچوقت به پایان نمیرسد؛ این نمونهای از اسکرول بینهایت است. سپس، وقتی روی آیکون خانه در منوی ناوبری X کلیک میکنیم، صفحه دوباره به بالای لیست برمیگردد. این آیکون دو وظیفه دارد: بهروزرسانی و بارگذاری دادههای جدید در For You و ارائه قابلیت اسکرول به بالا.
برای بهبود تجربه کاربری، بهتر است تمام پیادهسازیهای اسکرول بینهایت دارای گزینهای برای برگشت به بالای صفحه باشند. برای پیادهسازی این قابلیت در React، از ویژگی
scrollTop()
و هوک useRef()
استفاده خواهیم کرد تا کنترل بهتری بر موقعیت اسکرول داشته باشیم.
ترکیب این دو، امکان پیادهسازی قابلیتهایی مانند دکمههای اسکرول به بالا یا بارگذاری محتوای پویا هنگام اسکرول را فراهم میکند، همانطور که در مثالی که از ابتدا پیادهسازی کردیم دیدیم. این قابلیت را در فایل
App.tsx
پیادهسازی میکنیم:
import React, { useRef } from 'react'; function App() { const scrollableDiv = useRef<HTMLDivElement | null>(null); // scroll logic</span> const scrollToTop = () => { if (scrollableDiv.current) { scrollableDiv.current.scrollTop = 0; } }; return ( <div ref={scrollableDiv}> <!-- Infinite Scroll content --> <button onClick={scrollToTop}>Scroll to Top</button> </div> ); } export default ScrollableComponent;
در کد بالا، اسکرول به بالا با استفاده از
ref
و scrollTop()
انجام میشود که مستقیماً موقعیت اسکرول div
را دستکاری میکند. با این کار، میتوانیم تجربه کاربری در اسکرول بینهایت را بهطور قابلتوجهی بهبود دهیم.
اسکرول بینهایت یک تکنیک قدرتمند در طراحی وب است که تجربه کاربری را با بارگذاری تدریجی محتوا هنگام اسکرول به پایین صفحه بهبود میبخشد و نیاز به صفحهبندی را از بین میبرد. در این مقاله، سه روش مختلف برای پیادهسازی اسکرول بینهایت در برنامههای React را بررسی کردیم و یک صفحه محصولات برای یک فروشگاه اینترنتی ایجاد نمودیم.
هر تکنیک مزایا و معایب خاص خود را دارد، بنابراین انتخاب روش مناسب بستگی به نیازهای خاص پروژه ما و کاربران دارد. با پیادهسازی اسکرول بینهایت در برنامههای React، میتوانیم یک تجربه کاربری جذاب و روان ایجاد کنیم که کاربران را به تعامل بیشتر با محتوای ما ترغیب کند.
دیدگاهها: