React Suspense ব্যবহার করে সমান্তরাল ডেটা ফেচিং-এর উন্নত কৌশলগুলি অন্বেষণ করুন, অ্যাপ্লিকেশন পারফরম্যান্স এবং ব্যবহারকারীর অভিজ্ঞতা উন্নত করুন।
React Suspense Coordination: সমান্তরাল ডেটা ফেচিং-এ দক্ষতা অর্জন
React Suspense অ্যাসিঙ্ক্রোনাস অপারেশন, বিশেষ করে ডেটা ফেচিং-এর পরিচালনা করার পদ্ধতিতে বিপ্লব এনেছে। এটি কম্পোনেন্টগুলিকে ডেটা লোড হওয়ার জন্য অপেক্ষা করার সময় রেন্ডারিং "suspend" করার অনুমতি দেয়, যা লোডিং স্টেট পরিচালনা করার একটি ডিক্লারেটিভ উপায় সরবরাহ করে। তবে, পৃথক ডেটা ফেচগুলিকে Suspense দিয়ে মোড়ানো একটি জলপ্রপাতের প্রভাব তৈরি করতে পারে, যেখানে একটি ফেচ পরবর্তীটি শুরু হওয়ার আগে শেষ হয়, যা পারফরম্যান্সকে নেতিবাচকভাবে প্রভাবিত করে। এই ব্লগ পোস্টটি Suspense ব্যবহার করে সমান্তরালভাবে একাধিক ডেটা ফেচ সমন্বয় করার উন্নত কৌশলগুলির গভীরে প্রবেশ করে, আপনার অ্যাপ্লিকেশনের প্রতিক্রিয়াশীলতা অপ্টিমাইজ করে এবং বিশ্বব্যাপী দর্শকদের জন্য ব্যবহারকারীর অভিজ্ঞতা উন্নত করে।
ডেটা ফেচিং-এ জলপ্রপাতের সমস্যা বোঝা
এমন একটি পরিস্থিতির কল্পনা করুন যেখানে আপনাকে ব্যবহারকারীর নাম, অ্যাভাটার এবং সাম্প্রতিক কার্যকলাপ সহ একটি ব্যবহারকারী প্রোফাইল প্রদর্শন করতে হবে। যদি আপনি প্রতিটি ডেটা অংশ ক্রমানুসারে ফেচ করেন, তবে ব্যবহারকারী নামের জন্য একটি লোডিং স্পিনার, তারপর অ্যাভাটারের জন্য একটি এবং অবশেষে, অ্যাক্টিভিটি ফিডের জন্য একটি দেখতে পায়। এই ক্রমানুসারে লোডিং প্যাটার্ন একটি জলপ্রপাতের প্রভাব তৈরি করে, সম্পূর্ণ প্রোফাইলের রেন্ডারিং বিলম্বিত করে এবং ব্যবহারকারীদের হতাশ করে। বিভিন্ন নেটওয়ার্ক স্পিডযুক্ত আন্তর্জাতিক ব্যবহারকারীদের জন্য, এই বিলম্ব আরও বেশি স্পষ্ট হতে পারে।
এই সরলীকৃত কোড স্নিপেটটি বিবেচনা করুন:
function UserProfile() {
const name = useName(); // ব্যবহারকারীর নাম ফেচ করে
const avatar = useAvatar(name); // নামের উপর ভিত্তি করে অ্যাভাটার ফেচ করে
const activity = useActivity(name); // নামের উপর ভিত্তি করে কার্যকলাপ ফেচ করে
return (
<div>
<h2>{name}</h2>
<img src={avatar} alt="User Avatar" />
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
</div>
);
}
এই উদাহরণে, useAvatar এবং useActivity useName এর ফলাফলের উপর নির্ভরশীল। এটি একটি স্পষ্ট জলপ্রপাত তৈরি করে – useAvatar এবং useActivity ডেটা ফেচিং শুরু করতে পারে না যতক্ষণ না useName শেষ হয়। এটি অদক্ষ এবং একটি সাধারণ পারফরম্যান্স বাধা।
Suspense সহ সমান্তরাল ডেটা ফেচিং-এর কৌশল
Suspense সহ ডেটা ফেচিং অপ্টিমাইজ করার মূল বিষয় হল সমস্ত ডেটা অনুরোধ সমকালীনভাবে শুরু করা। এখানে কয়েকটি কৌশল রয়েছে যা আপনি ব্যবহার করতে পারেন:
1. `React.preload` এবং রিসোর্স ব্যবহার করে ডেটা প্রি-লোড করা
সবচেয়ে শক্তিশালী কৌশলগুলির মধ্যে একটি হল কম্পোনেন্ট রেন্ডার হওয়ার আগেই ডেটা প্রি-লোড করা। এতে একটি "রিসোর্স" (ডেটা ফেচিং প্রমিস এনক্যাপসুলেট করা একটি অবজেক্ট) তৈরি করা এবং ডেটা প্রি-ফেচ করা জড়িত। `React.preload` এটিতে সহায়তা করে। যখন কম্পোনেন্টের ডেটা প্রয়োজন হয়, তখন এটি ইতিমধ্যে উপলব্ধ থাকে, প্রায় সম্পূর্ণভাবে লোডিং স্টেট দূর করে।
একটি পণ্যের জন্য একটি রিসোর্স বিবেচনা করুন:
const createProductResource = (productId) => {
let promise;
let product;
let error;
const suspender = new Promise((resolve, reject) => {
promise = fetch(`/api/products/${productId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
product = data;
resolve();
})
.catch(e => {
error = e;
reject(e);
});
});
return {
read() {
if (error) {
throw error;
}
if (product) {
return product;
}
throw suspender;
},
};
};
// ব্যবহার:
const productResource = createProductResource(123);
function ProductDetails() {
const product = productResource.read();
return (<div>{product.name}</div>);
}
এখন, ProductDetails কম্পোনেন্ট রেন্ডার হওয়ার আগেই আপনি এই রিসোর্সটি প্রি-লোড করতে পারেন। উদাহরণস্বরূপ, রুট পরিবর্তনের সময় বা হোভার করার সময়।
React.preload(productResource);
এটি নিশ্চিত করে যে ProductDetails কম্পোনেন্টটির ডেটার প্রয়োজন হওয়ার সময় ডেটা সম্ভবত উপলব্ধ থাকবে, লোডিং স্টেট কমিয়ে বা সম্পূর্ণভাবে বাদ দেবে।
2. সমকালীন ডেটা ফেচিং-এর জন্য `Promise.all` ব্যবহার করা
আরেকটি সহজ এবং কার্যকর পদ্ধতি হল একটি একক Suspense সীমার মধ্যে সমস্ত ডেটা ফেচ সমকালীনভাবে শুরু করতে Promise.all ব্যবহার করা। ডেটা নির্ভরতাগুলি আগে থেকেই জানা থাকলে এটি ভাল কাজ করে।
ব্যবহারকারীর প্রোফাইল উদাহরণে ফিরে যাওয়া যাক। ডেটা ক্রমানুসারে ফেচ করার পরিবর্তে, আমরা নাম, অ্যাভাটার এবং অ্যাক্টিভিটি ফিড সমকালীনভাবে ফেচ করতে পারি:
import { useState, useEffect, Suspense } from 'react';
async function fetchName() {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Posted a photo' },
{ id: 2, text: 'Updated profile' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
function Name() {
const name = useSuspense(fetchName());
return <h2>{name}</h2>;
}
function Avatar({ name }) {
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="User Avatar" />;
}
function Activity({ name }) {
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const name = useSuspense(fetchName());
return (
<div>
<Suspense fallback=<div>Loading Avatar...</div>>
<Avatar name={name} />
</Suspense>
<Suspense fallback=<div>Loading Activity...</div>>
<Activity name={name} />
</Suspense>
</div>
);
}
export default UserProfile;
তবে, যদি `Avatar` এবং `Activity` উভয়ই `fetchName`-এর উপর নির্ভর করে, তবে আলাদা সাসপেন্স সীমার মধ্যে রেন্ডার করা হয়, আপনি `fetchName` প্রমিসকে প্যারেন্টে তুলে নিয়ে React Context এর মাধ্যমে সরবরাহ করতে পারেন।
import React, { createContext, useContext, useState, useEffect, Suspense } from 'react';
async function fetchName() {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Posted a photo' },
{ id: 2, text: 'Updated profile' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
const NamePromiseContext = createContext(null);
function Avatar() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="User Avatar" />;
}
function Activity() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const namePromise = fetchName();
return (
<NamePromiseContext.Provider value={namePromise}>
<Suspense fallback=<div>Loading Avatar...</div>>
<Avatar />
</Suspense>
<Suspense fallback=<div>Loading Activity...</div>>
<Activity />
</Suspense>
</NamePromiseContext.Provider>
);
}
export default UserProfile;
3. সমান্তরাল ফেচ পরিচালনা করার জন্য একটি কাস্টম হুক ব্যবহার করা
সম্ভাব্য শর্তাধীন ডেটা নির্ভরতা সহ আরও জটিল পরিস্থিতির জন্য, আপনি সমান্তরাল ডেটা ফেচিং পরিচালনা করতে এবং Suspense ব্যবহার করতে পারে এমন একটি রিসোর্স ফিরিয়ে দিতে একটি কাস্টম হুক তৈরি করতে পারেন।
import { useState, useEffect, useRef } from 'react';
function useParallelData(fetchFunctions) {
const [resource, setResource] = useState(null);
const mounted = useRef(true);
useEffect(() => {
mounted.current = true;
const promises = fetchFunctions.map(fn => fn());
const suspender = Promise.all(promises).then(
(results) => {
if (mounted.current) {
setResource({ status: 'success', value: results });
}
},
(error) => {
if (mounted.current) {
setResource({ status: 'error', value: error });
}
}
);
setResource({
status: 'pending',
value: suspender,
});
return () => {
mounted.current = false;
};
}, [fetchFunctions]);
const read = () => {
if (!resource) {
throw new Error('Resource not yet initialized');
}
if (resource.status === 'pending') {
throw resource.value;
}
if (resource.status === 'error') {
throw resource.value;
}
return resource.value;
};
return { read };
}
// উদাহরণ ব্যবহার:
async function fetchUserData(userId) {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 300));
return { id: userId, name: 'User ' + userId };
}
async function fetchUserPosts(userId) {
// API কল অনুকরণ করুন
await new Promise(resolve => setTimeout(resolve, 500));
return [{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }];
}
function UserProfile({ userId }) {
const { read } = useParallelData([
() => fetchUserData(userId),
() => fetchUserPosts(userId),
]);
const [userData, userPosts] = read();
return (
<div>
<h2>{userData.name}</h2>
<ul>
{userPosts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</div>
);
}
function App() {
return (
<Suspense fallback=<div>Loading user data...</div>>
<UserProfile userId={123} />
</Suspense>
);
}
export default App;
এই পদ্ধতিটি প্রমিস এবং লোডিং স্টেট পরিচালনার জটিলতা হুক-এর মধ্যে আবদ্ধ করে, কম্পোনেন্ট কোডকে পরিচ্ছন্ন এবং রেন্ডারিং ডেটার উপর বেশি মনোযোগী করে তোলে।
4. স্ট্রিমিং সার্ভার রেন্ডারিং সহ নির্বাচনী হাইড্রেশন
সার্ভার-রেন্ডার করা অ্যাপ্লিকেশনগুলির জন্য, React 18 স্ট্রিমিং সার্ভার রেন্ডারিং সহ নির্বাচনী হাইড্রেশন চালু করেছে। এটি আপনাকে সার্ভারে উপলব্ধ হওয়ার সাথে সাথে HTML-কে খণ্ডে খণ্ডে ক্লায়েন্টে পাঠাতে দেয়। আপনি ধীর-লোডিং কম্পোনেন্টগুলিকে <Suspense> সীমানা দিয়ে ঘিরে ফেলতে পারেন, যখন ধীর কম্পোনেন্টগুলি সার্ভারে লোড হচ্ছে তখন পৃষ্ঠার বাকি অংশকে ইন্টারেক্টিভ হতে দেয়। এটি অনুভূত পারফরম্যান্সকে নাটকীয়ভাবে উন্নত করে, বিশেষ করে ধীর নেটওয়ার্ক সংযোগ বা ডিভাইসযুক্ত ব্যবহারকারীদের জন্য।
একটি পরিস্থিতি বিবেচনা করুন যেখানে একটি সংবাদ ওয়েবসাইটের বিশ্বের বিভিন্ন অঞ্চল (যেমন, এশিয়া, ইউরোপ, আমেরিকা) থেকে নিবন্ধ প্রদর্শন করতে হবে। কিছু ডেটা উৎস অন্যদের তুলনায় ধীর হতে পারে। নির্বাচনী হাইড্রেশন দ্রুততর অঞ্চলের নিবন্ধগুলি প্রথমে প্রদর্শন করার অনুমতি দেয়, যখন ধীর অঞ্চলেরগুলি এখনও লোড হচ্ছে, পুরো পৃষ্ঠাটিকে ব্লক করা থেকে বিরত রাখে।
ত্রুটি এবং লোডিং অবস্থা পরিচালনা করা
যদিও Suspense লোডিং অবস্থা পরিচালনা সহজ করে তোলে, ত্রুটি পরিচালনা এখনও গুরুত্বপূর্ণ। ত্রুটি সীমানা (componentDidCatch লাইফসাইকেল পদ্ধতি বা `react-error-boundary`-এর মতো লাইব্রেরি থেকে useErrorBoundary হুক ব্যবহার করে) ডেটা ফেচিং বা রেন্ডারিংয়ের সময় ত্রুটিগুলি সহজভাবে পরিচালনা করার অনুমতি দেয়। এই ত্রুটি সীমানাগুলি নির্দিষ্ট Suspense সীমার মধ্যে ত্রুটিগুলি ধরতে কৌশলগতভাবে স্থাপন করা উচিত, পুরো অ্যাপ্লিকেশনটিকে ক্র্যাশ হওয়া থেকে রক্ষা করে।
import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
function MyComponent() {
// ... ডেটা ফেচ করে যা ত্রুটি হতে পারে
}
function App() {
return (
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</ErrorBoundary>
);
}
লোডিং এবং ত্রুটির উভয় অবস্থার জন্য তথ্যপূর্ণ এবং ব্যবহারকারী-বান্ধব ফলব্যাক UI প্রদান করতে মনে রাখবেন। এটি বিশেষ করে আন্তর্জাতিক ব্যবহারকারীদের জন্য গুরুত্বপূর্ণ যারা ধীর নেটওয়ার্ক গতি বা আঞ্চলিক পরিষেবা বিভ্রাটের সম্মুখীন হতে পারে।
Suspense সহ ডেটা ফেচিং অপ্টিমাইজ করার জন্য সেরা অনুশীলন
- গুরুত্বপূর্ণ ডেটা সনাক্ত করুন এবং অগ্রাধিকার দিন: আপনার অ্যাপ্লিকেশনের প্রাথমিক রেন্ডারিংয়ের জন্য কোন ডেটা অপরিহার্য তা নির্ধারণ করুন এবং সেই ডেটা প্রথমে ফেচ করার অগ্রাধিকার দিন।
- যখন সম্ভব ডেটা প্রি-লোড করুন: কম্পোনেন্টগুলির প্রয়োজন হওয়ার আগেই ডেটা প্রি-লোড করতে `React.preload` এবং রিসোর্স ব্যবহার করুন, লোডিং স্টেটগুলি হ্রাস করুন।
- সমকালীনভাবে ডেটা ফেচ করুন: সমান্তরালে একাধিক ডেটা ফেচ শুরু করতে `Promise.all` বা কাস্টম হুকগুলি ব্যবহার করুন।
- API এন্ডপয়েন্টগুলি অপ্টিমাইজ করুন: নিশ্চিত করুন যে আপনার API এন্ডপয়েন্টগুলি পারফরম্যান্সের জন্য অপ্টিমাইজ করা হয়েছে, লেটেন্সি এবং পেলোড সাইজ কমিয়ে। আপনি যা চান তা ফেচ করতে GraphQL-এর মতো কৌশলগুলি বিবেচনা করুন।
- ক্যাচিং প্রয়োগ করুন: API অনুরোধের সংখ্যা কমাতে ঘন ঘন অ্যাক্সেস করা ডেটা ক্যাচ করুন। শক্তিশালী ক্যাচিং ক্ষমতার জন্য `swr` বা `react-query`-এর মতো লাইব্রেরি ব্যবহার করার কথা বিবেচনা করুন।
- কোড স্প্লিটিং ব্যবহার করুন: প্রাথমিক লোড সময় কমাতে আপনার অ্যাপ্লিকেশনটিকে ছোট ছোট খণ্ডে বিভক্ত করুন। কোড স্প্লিটিং-কে Suspense-এর সাথে একত্রিত করুন আপনার অ্যাপ্লিকেশনের বিভিন্ন অংশকে ক্রমান্বয়ে লোড এবং রেন্ডার করতে।
- পারফরম্যান্স পর্যবেক্ষণ করুন: পারফরম্যান্সের বাধাগুলি সনাক্ত করতে এবং সমাধান করতে Lighthouse বা WebPageTest-এর মতো সরঞ্জাম ব্যবহার করে আপনার অ্যাপ্লিকেশনের পারফরম্যান্স নিয়মিত পর্যবেক্ষণ করুন।
- সহজভাবে ত্রুটিগুলি পরিচালনা করুন: ডেটা ফেচিং এবং রেন্ডারিংয়ের সময় ত্রুটিগুলি ধরতে ত্রুটি সীমানা প্রয়োগ করুন, ব্যবহারকারীদের তথ্যপূর্ণ ত্রুটি বার্তা সরবরাহ করুন।
- সার্ভার-সাইড রেন্ডারিং (SSR) বিবেচনা করুন: SEO এবং পারফরম্যান্সের জন্য, একটি দ্রুত প্রাথমিক অভিজ্ঞতা সরবরাহ করতে স্ট্রিমিং এবং নির্বাচনী হাইড্রেশন সহ SSR ব্যবহার করার কথা বিবেচনা করুন।
উপসংহার
React Suspense, সমান্তরাল ডেটা ফেচিং-এর কৌশলগুলির সাথে মিলিত হয়ে, প্রতিক্রিয়াশীল এবং পারফরম্যান্ট ওয়েব অ্যাপ্লিকেশন তৈরির জন্য একটি শক্তিশালী টুলকিট সরবরাহ করে। জলপ্রপাতের সমস্যা বোঝা এবং প্রি-লোডিং, Promise.all সহ সমকালীন ফেচিং এবং কাস্টম হুকগুলির মতো কৌশলগুলি বাস্তবায়ন করে, আপনি ব্যবহারকারীর অভিজ্ঞতাকে উল্লেখযোগ্যভাবে উন্নত করতে পারেন। সহজভাবে ত্রুটিগুলি পরিচালনা করতে এবং আপনার অ্যাপ্লিকেশন বিশ্বব্যাপী ব্যবহারকারীদের জন্য অপ্টিমাইজ করা হয়েছে তা নিশ্চিত করতে পারফরম্যান্স পর্যবেক্ষণ করতে ভুলবেন না। React বিকশিত হতে থাকায়, স্ট্রিমিং সার্ভার রেন্ডারিং সহ নির্বাচনী হাইড্রেশন-এর মতো নতুন বৈশিষ্ট্যগুলি অন্বেষণ করা লোকেশন বা নেটওয়ার্ক পরিস্থিতি নির্বিশেষে ব্যতিক্রমী ব্যবহারকারীর অভিজ্ঞতা সরবরাহ করার আপনার ক্ষমতাকে আরও উন্নত করবে। এই কৌশলগুলি গ্রহণ করে, আপনি এমন অ্যাপ্লিকেশন তৈরি করতে পারেন যা কেবল কার্যকরী নয়, বিশ্বব্যাপী দর্শকদের জন্য ব্যবহার করাও আনন্দদায়ক।
এই ব্লগ পোস্টটি React Suspense সহ সমান্তরাল ডেটা ফেচিং কৌশলগুলির একটি ব্যাপক ওভারভিউ সরবরাহ করার লক্ষ্য। আমরা আশা করি আপনি এটি তথ্যপূর্ণ এবং সহায়ক পেয়েছেন। আমরা আপনাকে আপনার নিজস্ব প্রকল্পগুলিতে এই কৌশলগুলি নিয়ে পরীক্ষা করার এবং আপনার অনুসন্ধানগুলি সম্প্রদায়ের সাথে ভাগ করে নেওয়ার জন্য উত্সাহিত করি।