Skip to content

iDevRanjan/nexus-store

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NexusStore: রিয়্যাক্ট স্টেট ম্যানেজমেন্টের একটি কমপ্লিট এবং অপটিমাইজড গাইড

NexusStore হলো রিয়্যাক্ট অ্যাপ্লিকেশনের জন্য তৈরি করা একটি অত্যন্ত লাইটওয়েট এবং পারফরম্যান্স-বান্ধব কাস্টম স্টেট ম্যানেজমেন্ট সলিউশন। সাধারণ এবং জটিল—উভয় ধরনের অ্যাপ্লিকেশনের স্টেট মেইনটেইন করার জন্য এটি মূলত তিনটি প্রধান উপাদানের ওপর ভিত্তি করে কাজ করে: স্টোর তৈরি করার জন্য createStore, রিয়্যাক্ট কম্পোনেন্টে ডেটা সাবস্ক্রাইব করার জন্য useStore হুক, এবং নেস্টেড ডেটা খুব সহজে হ্যান্ডেল করার জন্য immer (ঐচ্ছিক) র‍্যাপার।

এই আর্টিকেলে আমরা NexusStore-এর একদম বেসিক থেকে শুরু করে অ্যাডভান্সড আর্কিটেকচার এবং অপটিমাইজেশন টেকনিক নিয়ে বিস্তারিত আলোচনা করব, যা আপনাকে একটি ক্লিন এবং হাই-পারফর্মিং অ্যাপ্লিকেশন তৈরি করতে সাহায্য করবে।

স্টোর তৈরি এবং প্রাথমিক সেটআপ

NexusStore-এ সবকিছুর শুরু হয় createStore ফাংশনের মাধ্যমে। এই ফাংশনটিতে আপনার অ্যাপ্লিকেশনের প্রাথমিক ডেটা বা ইনিশিয়াল স্টেট (initial state) একটি অবজেক্ট আকারে পাস করতে হয়। এর বিনিময়ে এটি একটি সম্পূর্ণ স্টোর অবজেক্ট রিটার্ন করে, যা পরবর্তীতে ডেটা ম্যানেজ করার জন্য ব্যবহৃত হয়।

import { createStore } from "../NexusStore/core";

const countStore = createStore({
    count: 0,
});

এই createStore ফাংশনটিকে আপনি আপনার প্রজেক্টের সুবিধামতো যেকোনো স্থানে কল করতে পারেন। পুরো অ্যাপ্লিকেশনের গ্লোবাল স্টেটের জন্য চাইলে একটি ডেডিকেটেড store.js ফাইলে এটি তৈরি করে রাখা যায়। আবার, স্টোরটি যদি শুধুমাত্র একটি নির্দিষ্ট কম্পোনেন্টের জন্য প্রয়োজন হয়, তবে সরাসরি সেই কম্পোনেন্ট ফাইলের ভেতরেও এটিকে রাখা সম্ভব।

তবে এক্ষেত্রে একটি অত্যন্ত গুরুত্বপূর্ণ নিয়ম সবসময় মনে রাখতে হবে—স্টোর তৈরির এই প্রক্রিয়াটি সর্বদা রিয়্যাক্ট কম্পোনেন্ট ফাংশনের বাইরে করতে হবে। যদি ভুলবশত createStore কে কম্পোনেন্টের ভেতরে কল করা হয়, তবে রিয়্যাক্টের প্রতিটি রেন্ডার সাইকেলে মেমোরিতে একটি করে নতুন স্টোর তৈরি হতে থাকবে। এর ফলে অ্যাপ্লিকেশনের চলমান স্টেট সম্পূর্ণ মুছে গিয়ে ভেঙে পড়বে, যা একটি মারাত্মক ভুল হিসেবে গণ্য হয়। তাই স্টোর সবসময় কম্পোনেন্টের স্কোপের বাইরে গ্লোবালি ডিক্লেয়ার করা উচিত।

স্টোর থেকে ডেটা সাবস্ক্রাইব করা: useStore হুকের ব্যবহার

স্টোর তৈরি করার পর, রিয়্যাক্ট কম্পোনেন্টে সেই স্টোরের ডেটা ব্যবহার করতে এবং পরিবর্তনের সাথে সিঙ্ক (sync) বা সাবস্ক্রাইব করে রাখতে useStore হুকটি ব্যবহার করা হয়। স্টোরের নির্দিষ্ট কোনো ডেটা আপডেট হলে এই হুকটি স্বয়ংক্রিয়ভাবে কম্পোনেন্টটিকে রি-রেন্ডার করে এবং নতুন আপডেট হওয়া ডেটাটি রিটার্ন করে।

import { useStore } from "../NexusStore/useStore";

const countValue = useStore(countStore, (state) => state.count);

useStore হুকটি মূলত দুটি আর্গুমেন্ট গ্রহণ করে।

  • প্রথম আর্গুমেন্ট: আপনাকে সেই স্টোরের রেফারেন্সটি পাস করতে হবে, যেখান থেকে আপনি ডেটা সংগ্রহ করতে চান।

  • দ্বিতীয় আর্গুমেন্ট: একটি "সিলেক্টর ফাংশন" (selector function) দিতে হয়। এই সিলেক্টর ফাংশনটি আর্গুমেন্ট হিসেবে পুরো স্টোরের স্টেটটি গ্রহণ করে এবং সেখান থেকে শুধুমাত্র আপনার দরকারি স্টেটের অংশটুকু আলাদা করে রিটার্ন করে।

সিলেক্টর ফাংশনের প্রধান কাজ হলো পুরো স্টোরের পরিবর্তে শুধুমাত্র নির্দিষ্ট একটি ভ্যালুকে সাবস্ক্রাইব করে রাখা। এর সবচেয়ে বড় সুবিধা হলো, স্টোরের অন্য কোনো ডেটা পরিবর্তন হলেও এই নির্দিষ্ট কম্পোনেন্টটি অকারণে রি-রেন্ডার হবে না।

উদাহরণস্বরূপ, যদি আপনি শুধু count সাবস্ক্রাইব করে থাকেন, তবে স্টোরের অন্য কোনো স্টেট আপডেট হলেও রেন্ডারিং সাইকেল ট্রিগার হবে না; শুধুমাত্র count পরিবর্তন হলেই কম্পোনেন্টটি নতুন ডেটা নিয়ে রি-রেন্ডার হবে।

// পুরো স্টোর সাবস্ক্রাইব করা — স্টোরের যেকোনো পরিবর্তনেই কম্পোনেন্টটি রি-রেন্ডার হবে
const fullState = useStore(countStore);

// নির্দিষ্ট ডেটা সাবস্ক্রাইব করা — শুধুমাত্র count পরিবর্তন না হলে অকারণে রি-রেন্ডার হবে না
const countValue = useStore(countStore, (state) => state.count);

যদি আপনি হুকটিতে কোনো সিলেক্টর ফাংশন পাস না করেন (শুধুমাত্র স্টোরের রেফারেন্স দেন), তবে useStore সম্পূর্ণ স্টোরের ডেটাটিকেই রিটার্ন করে দেয়। কিন্তু এর ফলে স্টোরের যেকোনো ছোটখাটো পরিবর্তনেও কম্পোনেন্টটি রি-রেন্ডার হতে থাকে, যা বড় অ্যাপ্লিকেশনের ক্ষেত্রে মেমোরি ও পারফরম্যান্সের ওপর বিরূপ প্রভাব ফেলতে পারে। তাই রিয়্যাক্ট অ্যাপের পারফরম্যান্স সর্বোচ্চ পর্যায়ে অপটিমাইজ রাখার জন্য সবসময় সিলেক্টর ফাংশন ব্যবহার করাই হলো সর্বোত্তম প্র্যাকটিস।

setState এর মাধ্যমে ডেটা বা স্টেট আপডেট করা

স্টোরের কোনো নির্দিষ্ট ডেটা আপডেট করার জন্য setState মেথডটি ব্যবহার করা হয়। এটি অত্যন্ত সুনির্দিষ্টভাবে কাজ করে এবং ডেটা পরিবর্তনের জন্য মূলত দুটি আর্গুমেন্ট গ্রহণ করে।

প্রথম আর্গুমেন্ট: কি-সিলেক্টর (Key Selector) ফাংশন

setState এর প্রথম আর্গুমেন্ট হিসেবে একটি ফাংশন পাস করতে হয়। এই ফাংশনটি আপনার স্টোরের ইনিশিয়াল স্টেটে থাকা সমস্ত টপ-লেভেল কি (top-level key)-গুলোকে একটি অবজেক্ট আকারে গ্রহণ করে এবং আপনি যে ডেটাটি আপডেট করতে চান, তার নির্দিষ্ট কি-টি রিটার্ন করে। উদাহরণস্বরূপ, যদি আপনার স্টোরের ইনিশিয়াল স্টেট { count: 0, theme: "light" } হয়, তবে এই ফাংশনটি আর্গুমেন্ট হিসেবে { count: "count", theme: "theme" } অবজেক্টটি পাবে।

countStore.setState(
    (keys) => keys.count, // "count" key-টি সিলেক্ট করা হচ্ছে
    // ...
);

এক্ষেত্রে একটি অত্যন্ত গুরুত্বপূর্ণ সীমাবদ্ধতা মনে রাখা প্রয়োজন—NexusStore-এর setState মেথডে শুধুমাত্র টপ-লেভেল কি (Top-level Key) সিলেক্ট করা যায়। আপনি চাইলেও কোনো নেস্টেড কি সিলেক্ট করে তার ভ্যালু পরিবর্তন করতে পারবেন না।

// ✅ সঠিক পদ্ধতি (Top-level Key)
(keys) => keys.cart

// ❌ ভুল পদ্ধতি (Nested Key সমর্থিত নয়)
(keys) => keys.cart.items

দ্বিতীয় আর্গুমেন্ট: নতুন ভ্যালু অথবা আপডেটার ফাংশন

দ্বিতীয় আর্গুমেন্টটি হলো ডেটার নতুন ভ্যালু, যা আপনি আপনার প্রয়োজন অনুযায়ী দু'টি ভিন্ন পদ্ধতিতে পাস করতে পারবেন।

সরাসরি ভ্যালু প্রদান (Direct Value)

প্রথম পদ্ধতিটি হলো সরাসরি নতুন ভ্যালু সেট করে দেওয়া। এক্ষেত্রে নিয়ম হলো, ইনিশিয়াল স্টেটে আপনি যে ডেটা টাইপ (যেমন: স্ট্রিং, নাম্বার, বা বুলিয়ান) ব্যবহার করেছিলেন, আপডেট করার সময়ও ঠিক একই টাইপের ডেটা প্রদান করতে হবে।

// count এর ইনিশিয়াল টাইপ ছিল number, তাই সরাসরি number পাস করা হচ্ছে
countStore.setState((keys) => keys.count, 10);

// theme এর ইনিশিয়াল টাইপ ছিল string
themeStore.setState((keys) => keys.theme, "dark");

ফাংশনাল আপডেট (Functional Update)

দ্বিতীয় পদ্ধতিটি হলো একটি আপডেটার ফাংশন পাস করা। যখন আপনার নতুন ডেটাটি স্টোরের বর্তমান ডেটার ওপর নির্ভরশীল হয়, তখন এই পদ্ধতিটি সবচেয়ে বেশি কার্যকর। এই ফাংশনটি প্রথম আর্গুমেন্টে সিলেক্ট করা নির্দিষ্ট কি-এর বর্তমান স্টেটটিকে (current state) আর্গুমেন্ট হিসেবে গ্রহণ করে এবং সেটির ওপর ভিত্তি করে নতুন একটি ভ্যালু রিটার্ন করে।

// বর্তমান count গ্রহণ করে তার সাথে ১ যোগ করা হচ্ছে
countStore.setState(
    (keys) => keys.count,
    (currentCount) => currentCount + 1,
);

// কাউন্ট যেন কোনোভাবেই নেগেটিভ না হয়, তাই মিনিমাম ০ রাখা হচ্ছে
countStore.setState(
    (keys) => keys.count,
    (currentCount) => Math.max(0, currentCount - 1),
);

// বর্তমান থিম যাচাই করে ডার্ক বা লাইট টগল করা হচ্ছে
themeStore.setState(
    (keys) => keys.theme,
    (currentTheme) => (currentTheme === "light" ? "dark" : "light"),
);

গুরুত্বপূর্ণ বিষয়: আপনি দ্বিতীয় আর্গুমেন্টে সরাসরি ভ্যালু দিন বা ফাংশনাল অ্যাপ্রোচের মাধ্যমে নতুন ভ্যালু রিটার্ন করুন—উভয় ক্ষেত্রেই এটি প্রথম আর্গুমেন্টে সিলেক্ট করা কি-এর ওপর ভিত্তি করেই কাজ করবে। অর্থাৎ, এটি শুধুমাত্র নির্দিষ্ট সেই কি-এর ডেটাটিকেই আপডেট করবে এবং স্টোরের বাকি সমস্ত কি এবং ডেটা সম্পূর্ণ অপরিবর্তিত থাকবে। অ্যাপ্লিকেশনের ডেটা ফ্লো ডিবাগ করার প্রয়োজন হলে আপনি ফাংশনের ভেতর keys এবং ফাংশনাল আপডেটের currentState উভয়েরই console.log করে বর্তমান অবস্থা যাচাই করতে পারেন।

সিলেক্টর ফাংশনের স্ট্যাবিলিটি এবং রেন্ডার অপটিমাইজেশন

useStore হুকের মাধ্যমে স্টোর থেকে নির্দিষ্ট ডেটা সাবস্ক্রাইব করার সময় যে সিলেক্টর ফাংশনটি পাস করা হয়, সেটি যদি সরাসরি ইনলাইন (inline) লেখা হয়, তবে রিয়্যাক্টের প্রতিটি রেন্ডার সাইকেলে ফাংশনটির একটি করে নতুন ইনস্ট্যান্স বা রেফারেন্স তৈরি হয়। রিয়্যাক্ট ইন্টারনালি এই রেফারেন্স পরিবর্তনগুলো ডিটেক্ট করে এবং অকারণে অতিরিক্ত প্রসেসিং করতে থাকে। বড় অ্যাপ্লিকেশনের ক্ষেত্রে এটি পারফরম্যান্সে নেতিবাচক প্রভাব ফেলতে পারে। এই অতিরিক্ত প্রসেসিং এড়িয়ে চলার এবং পারফরম্যান্স অপটিমাইজ করার দুটি অত্যন্ত কার্যকরী পদ্ধতি রয়েছে।

সবচেয়ে ক্লিন এবং সর্বোত্তম পদ্ধতিটি হলো সিলেক্টর ফাংশনটিকে কম্পোনেন্টের ঠিক বাইরে গ্লোবাল স্কোপে ডিক্লেয়ার করা। কম্পোনেন্টের বাইরে ফাংশন ডিফাইন করার ফলে, কম্পোনেন্টটি বারবার রি-রেন্ডার হলেও ফাংশনটির রেফারেন্স একদম অপরিবর্তিত বা স্ট্যাবল থাকে। ফলে রিয়্যাক্টকে ইন্টারনালি কোনো অতিরিক্ত কাজ করতে হয় না।

// সর্বোত্তম পদ্ধতি: কম্পোনেন্টের বাইরে সিলেক্টর ডিফাইন করলে স্ট্যাবল রেফারেন্স মেইনটেইন হয়
const cartItemsSelector = (state) => state.cart.items;

export default function CartComponent() {
    const cartItems = useSelector(cartItemsSelector);
    // ...
}

দ্বিতীয় পদ্ধতিটি হলো রিয়্যাক্টের নিজস্ব useCallback হুক ব্যবহার করা। যদি কোনো বিশেষ লজিকের কারণে আপনাকে কম্পোনেন্টের ভেতরেই সিলেক্টর ফাংশন লিখতে হয়, তবে সেটিকে সরাসরি ইনলাইন না লিখে useCallback এর মাধ্যমে র‍্যাপ করে একটি স্ট্যাবল রেফারেন্স তৈরি করে নেওয়া উচিত।

export default function CartComponent() {
    // useCallback এর মাধ্যমে স্ট্যাবল রেফারেন্স তৈরি করা হলো
    const cartItems = useSelector(useCallback((state) => state.cart.items, []));
    // ...
}

সরাসরি ইনলাইন সিলেক্টর ফাংশন ব্যবহার করলেও আপনার অ্যাপ্লিকেশনটি ডেটা নিয়ে সঠিকভাবে কাজ করবে এবং একে পুরোপুরি "ভুল" বলা যাবে না।

// কাজ করবে, কিন্তু অপটিমাইজেশনের দৃষ্টিকোণ থেকে এটি সেরা পদ্ধতি নয়
const cartItems = useSelector((state) => state.cart.items);

তাই হাই-পারফর্মিং অ্যাপ্লিকেশন তৈরির ক্ষেত্রে রিয়্যাক্টের অতিরিক্ত রেন্ডার বাঁচানোর জন্য ওপরের দুটি অপটিমাইজেশন পদ্ধতি অনুসরণ করাই একজন প্রফেশনাল ডেভেলপারের পরিচায়ক।

মাল্টিপল স্টোর এবং স্টেট আর্কিটেকচার ডিজাইন

একটি বড় এবং স্কেলেবল রিয়্যাক্ট অ্যাপ্লিকেশনে সব ডেটা একটিমাত্র স্টোরে না রেখে, ডেটার ধরন এবং প্রয়োজনীয়তা অনুযায়ী একাধিক আলাদা স্টোর তৈরি করা অনেক বেশি যৌক্তিক। উদাহরণস্বরূপ, ইউজারের অথেনটিকেশন (auth) ডেটা এবং অ্যাপ্লিকেশনের ইউজার ইন্টারফেসের থিম (theme) সম্পূর্ণ ভিন্ন দুটি বিষয়। এদের জন্য আপনি খুব সহজেই createStore এর মাধ্যমে আলাদা আলাদা স্টোর তৈরি করে নিতে পারেন।

// src/stores/store.js
export const themeStore = createStore({ theme: "light" });
export const authStore = createStore({ auth: { isLoggedin: false } });

একাধিক স্টোর ব্যবহারের ক্ষেত্রে একটি চমৎকার এবং ক্লিন-কোড প্র্যাকটিস হলো প্রতিটি স্টোরের জন্য আলাদা আলাদা কাস্টম হুক (custom hook) তৈরি করে নেওয়া। এতে করে আপনাকে অ্যাপ্লিকেশনের প্রতিটি কম্পোনেন্টে গিয়ে বারবার গ্লোবাল useStore হুক এবং নির্দিষ্ট স্টোরের রেফারেন্স ইম্পোর্ট করে পাস করতে হবে না।

// src/stores/hooks.js
import { useStore } from "../NexusStore/useStore";
import { authStore, themeStore } from "./store";

// থিমের জন্য কাস্টম হুক
export function useThemeStore(selector) {
    return useStore(themeStore, selector);
}

// অথেনটিকেশনের জন্য কাস্টম হুক
export function useAuthStore(selector) {
    return useStore(authStore, selector);
}

এই কাস্টম হুকগুলো তৈরি করার ফলে, যেকোনো কম্পোনেন্টে ডেটা সাবস্ক্রাইব করা অত্যন্ত সহজ এবং ছিমছাম হয়ে যায়। আপনাকে শুধু কাস্টম হুকটি কল করে সিলেক্টর ফাংশনটি পাস করতে হবে।

// ThemeComponent.jsx এ ব্যবহার:
const theme = useThemeStore((state) => state.theme);

// AuthComponent.jsx এ ব্যবহার:
const auth = useAuthStore((state) => state.auth);

স্টেট ডিজাইনের গোল্ডেন রুল

অ্যাপ্লিকেশনের আর্কিটেকচার ডিজাইন করার সময় স্টেট ম্যানেজমেন্টের ক্ষেত্রে সবসময় একটি "গোল্ডেন রুল" মেনে চলা উচিত। যদি আপনার অ্যাপ্লিকেশনে এমন কোনো হ্যান্ডলার বা অ্যাকশন থাকে, যার মাধ্যমে একাধিক স্টেটের ডেটা একইসাথে পরিবর্তন করার প্রয়োজন হয়, তবে সেই সম্পর্কিত ডেটাগুলোকে সবসময় একটি সিঙ্গেল বা কমন স্টোরের ভেতরে রাখার চেষ্টা করুন। এতে করে আপনি setMultiple মেথড ব্যবহার করে রিয়্যাক্টের একটিমাত্র রেন্ডার সাইকেলেই সম্পূর্ণ ডেটা আপডেট করতে পারবেন।

অন্যদিকে, যদি একটি ডেটার আপডেটের সাথে অন্য ডেটার আপডেটের কোনো সম্পর্ক বা সিঙ্ক (Sync) করার প্রয়োজন না থাকে (যেমন, ইউজার অথেনটিকেশন পরিবর্তনের সাথে ওয়েবসাইটের থিমের কোনো সম্পর্ক নেই), তবে তাদেরকে অবশ্যই আলাদা আলাদা স্টোরে রাখা উচিত। এর ফলে একটি স্টোরের ডেটা আপডেট হলে অন্য স্টোরটি সম্পূর্ণ সেফ বা আইসোলেটেড (isolated) থাকে এবং অ্যাপ্লিকেশনের পারফরম্যান্সে কোনো প্রকার নেতিবাচক প্রভাব পড়ে না।

Immer ইন্টিগ্রেশন: নেস্টেড স্টেট অত্যন্ত সহজে আপডেট করা

জাভাস্ক্রিপ্টে নেস্টেড অবজেক্ট বা অ্যারে আপডেট করা বেশ জটিল এবং সময়সাপেক্ষ একটি কাজ। ইমিউটেবিলিটি (immutability) বজায় রাখার জন্য ডেভেলপারদের বারবার স্প্রেড অপারেটর (...) ব্যবহার করে নতুন অবজেক্ট বা অ্যারে তৈরি করতে হয়। এই ঝামেলা থেকে সম্পূর্ণ মুক্তি দিতে NexusStore-এ রয়েছে Immer-এর চমৎকার একটি ইন্টিগ্রেশন। Immer-এর সবচেয়ে বড় সুবিধা হলো, এটি আপনাকে সরাসরি ড্রাফট (draft) ডেটা মিউটেট (mutate) বা পরিবর্তন করার অনুমতি দেয় এবং ইন্টারনালি নিজেই সেটিকে একটি নতুন ইমিউটেবল স্টেটে রূপান্তর করে নেয়। NexusStore-এ Immer-এর এই সুবিধাটি আপনি মূলত দুটি ভিন্ন পদ্ধতিতে গ্রহণ করতে পারেন।

প্রতিটি setState-এ সরাসরি produce এর ব্যবহার

প্রথম পদ্ধতিটি হলো স্টোর র‍্যাপার ছাড়াই ম্যানুয়ালি produce ফাংশনের ব্যবহার। যদি আপনি স্টোর তৈরি করার সময় Immer ব্যবহার না করে থাকেন, তবে যেকোনো নির্দিষ্ট ডেটা আপডেটের জন্য সরাসরি immer লাইব্রেরি থেকে produce ইম্পোর্ট করে সেটিকে ফাংশনাল আপডেটের ভেতর ব্যবহার করতে পারেন।

import { produce } from "immer";
import { authStore } from "../store/store";

authStore.setState(
    (keys) => keys.auth,
    produce((draft) => {
        draft.isLoggedin = !draft.isLoggedin;
    }),
);

গুরুত্বপূর্ণ নোট: এক্ষেত্রে একটি অত্যন্ত গুরুত্বপূর্ণ বিষয় মনে রাখা প্রয়োজন। সাধারণভাবে produce ব্যবহার করার সময় আমরা produce(baseState, recipe) আর্কিটেকচার ফলো করি। কিন্তু NexusStore-এ আপনাকে কোনো baseState পাস করতে হবে না। আপনি শুধু রেসিপি ফাংশনটি অর্থাৎ produce((draft) => { ... }) লিখে দেবেন। Immer এটিকে স্বয়ংক্রিয়ভাবে একটি কারিড (curried) ফাংশনে রূপান্তর করে নেয় এবং NexusStore নিজে থেকেই setState-এর বর্তমান স্টেটটিকে বেস-স্টেট হিসেবে Immer-এর কাছে পৌঁছে দেয়। এই পদ্ধতিটি বেশ কার্যকরী হলেও, এর একটি ছোট অসুবিধা হলো—প্রতিটি setState কলের সময় আপনাকে আলাদাভাবে produce ইম্পোর্ট করে র‍্যাপ করতে হয়।

স্টোরকে immer() দিয়ে র‍্যাপ করা (সবচেয়ে সুবিধাজনক পদ্ধতি)

দ্বিতীয় এবং সবচেয়ে প্রফেশনাল পদ্ধতিটি হলো স্টোর তৈরি করার সময়ই সেটিকে Immer-এর র‍্যাপার দিয়ে মুড়ে ফেলা। যদি আপনি চান যে আপনার নির্দিষ্ট কোনো স্টোরের সমস্ত setState এবং setMultiple কলে স্বয়ংক্রিয়ভাবে Immer-এর সুবিধা যুক্ত হোক, তবে createStore কে immer() ফাংশন দিয়ে র‍্যাপ করে নেওয়া হলো সর্বোত্তম সিদ্ধান্ত।

import { createStore } from "../NexusStore/core";
import { immer } from "../NexusStore/immer";

// স্টোরটিকে immer দিয়ে র‍্যাপ করা হলো
export const store = immer(createStore(state));

স্টোরটিকে একবার এভাবে র‍্যাপ করে নেওয়ার পর, ডেটা আপডেট করার সময় আপনাকে আর বারবার produce ইম্পোর্ট করতে হবে না। আপনি সরাসরি ফাংশনাল আপডেটের মাধ্যমে অত্যন্ত ক্লিন কোড লিখে স্টোরের ড্রাফট ডেটা মিউটেট করতে পারবেন।

// produce ছাড়াই সরাসরি অ্যারে মিউটেট (push) করা যাচ্ছে
store.setState(
    (keys) => keys.cart,
    (draft) => {
        draft.items.push("Kiwi");
    },
);

// produce ছাড়াই সরাসরি অ্যারে মিউটেট (pop) করা যাচ্ছে
store.setState(
    (keys) => keys.cart,
    (draft) => {
        draft.items.pop();
    },
);

এখানে একটি চমৎকার বিষয় হলো, স্টোরকে Immer দিয়ে র‍্যাপ করার পরও আপনি চাইলে আগের মতোই সরাসরি ভ্যালু (direct value) পাস করে স্টেট আপডেট করতে পারবেন। অর্থাৎ, Immer র‍্যাপার থাকা সত্ত্বেও store.setState((keys) => keys.theme, "dark") কোডটি সম্পূর্ণ নির্ভুলভাবে কাজ করবে। মূলত, আপনি যখন ফাংশনাল অ্যাপ্রোচ ব্যবহার করবেন, শুধুমাত্র তখনই লাইব্রেরিটি ইন্টারনালি Immer কে অ্যাক্টিভেট করে তার কাজ সম্পন্ন করবে।

setMultiple: একবারে একাধিক স্টেট আপডেট বা ব্যাচ আপডেট (Batch Updates)

যখন একই স্টোরের একাধিক কি (key) বা ডেটা একই সাথে আপডেট করার প্রয়োজন হয়, তখন বারবার setState ব্যবহার করা মোটেও উচিত নয়। কারণ, একই স্টোরের দুটো আলাদা কি যদি আপনি আলাদাভাবে setState দিয়ে আপডেট করেন, তবে স্টোরটি দু'বার নোটিফাই হবে এবং এর ফলে আপনার রিয়্যাক্ট কম্পোনেন্ট সম্ভাব্য দু'বার রি-রেন্ডার হতে পারে। এই পারফরম্যান্স ইস্যু এড়ানোর জন্য NexusStore-এ রয়েছে setMultiple মেথড, যা একাধিক আপডেটকে একটিমাত্র আপডেট সাইকেলে (update cycle) সম্পন্ন করে অ্যাপ্লিকেশনের পারফরম্যান্স সর্বোচ্চ পর্যায়ে অপটিমাইজ রাখে।

// ❌ এটি দুইবার setState কল করে — ফলে দুইটি আলাদা আপডেট সাইকেল তৈরি হয়
store.setState(
    (keys) => keys.auth,
    (draft) => {
        draft.isLoggedin = false;
    },
);
store.setState(
    (keys) => keys.theme,
    (draft) => {
        draft.mode = "light";
    },
);

// ✅ এটি একবারেই সব আপডেট সম্পন্ন করে — একটিমাত্র আপডেট সাইকেল
store.setMultiple((draft) => {
    draft.auth.isLoggedin = false;
    draft.theme.mode = "light";
    draft.theme.color = "#ffffff";
});

নিচে বিভিন্ন পরিস্থিতিতে setMultiple মেথডের ব্যবহারের পদ্ধতিগুলো বিস্তারিত আলোচনা করা হলো:

Immer র‍্যাপড স্টোরে setMultiple এর ব্যবহার (সবচেয়ে সহজ পদ্ধতি)

যদি আপনার স্টোরটি আগে থেকেই immer() দিয়ে র‍্যাপ করা থাকে, তবে setMultiple ব্যবহার করা সবচেয়ে সহজ ও সাবলীল। এক্ষেত্রে আপনি মেথডটিতে সরাসরি একটি ফাংশন পাস করতে পারেন, যা পুরো স্টোরের বর্তমান ডেটার একটি মিউটেবল কপি (mutable copy) বা "ড্রাফট (draft)" হিসেবে গ্রহণ করে। আপনি এই ড্রাফটের ওপর সরাসরি মিউটেশন (যেমন: ভ্যালু অ্যাসাইন করা) করতে পারবেন এবং আপনি যা যা পরিবর্তন করবেন, Immer সেই পরিবর্তনগুলো নিয়ে স্বয়ংক্রিয়ভাবে একটি নতুন ইমিউটেবল স্টেট তৈরি করে নেবে।

store.setMultiple((draft) => {
    draft.auth.isLoggedin = false;
    draft.theme.mode = "light";
    draft.theme.color = "#ffffff";
});

টিপস: কোড ডিবাগ করার সময় Immer ড্রাফটের বর্তমান ভ্যালু সরাসরি কনসোলে লগ করলে তা বোঝা যায় না। এর জন্য immer লাইব্রেরি থেকে current মেথডটি ইম্পোর্ট করে console.log(current(draft)) ব্যবহার করতে হবে।

Immer ছাড়া স্টোরে setMultiple এর ব্যবহার (সরাসরি অবজেক্ট বা ফাংশনাল অ্যাপ্রোচ)

যদি আপনার স্টোরটি Immer দিয়ে র‍্যাপ করা না থাকে, তবে আপনি setMultiple-এ সরাসরি একটি প্লেইন অবজেক্ট (plain object) পাস করতে পারেন। এই অবজেক্টের ভেতরে আপনি যে কি-গুলো দেবেন, লাইব্রেরিটি সেগুলোকে স্টোরের বর্তমান বা বিদ্যমান স্টেটের সাথে স্বয়ংক্রিয়ভাবে মার্জ (merge) করে নেবে। যেমন, আপনি শুধু auth এবং theme আপডেট করলে স্টোরের cart বা settings এর মতো অন্যান্য ডেটাগুলো সম্পূর্ণ অপরিবর্তিত থাকবে।

// সরাসরি অবজেক্ট পাস করে ডেটা মার্জ করা
store.setMultiple({
    auth: {
        isLoggedin: false,
    },
    theme: {
        mode: "light",
        color: "#ffffff",
    },
});

এছাড়া, আপনি চাইলে এখানে একটি ফাংশনাল অ্যাপ্রোচও ব্যবহার করতে পারেন। এই পদ্ধতিতে ফাংশনটি পুরো স্টোরের বর্তমান স্টেটকে আর্গুমেন্ট হিসেবে পাবে এবং কারেন্ট ভ্যালুর ওপর ভিত্তি করে আপডেট করা নতুন অবজেক্ট রিটার্ন করবে। মনে রাখা অত্যন্ত জরুরি যে, এই ফাংশনাল অ্যাপ্রোচে আপনাকে ম্যানুয়ালি ...state স্প্রেড (spread) করতে হবে না; NexusStore লাইব্রেরি নিজেই বাকি আনচেঞ্জড (unchanged) বা অপরিবর্তিত স্টেটগুলোকে সযত্নে ধরে রাখবে।

// ফাংশনাল অ্যাপ্রোচে বর্তমান ডেটার ওপর ভিত্তি করে আপডেট
store.setMultiple((state) => ({
    auth: {
        isLoggedin: !state.auth.isLoggedin, // বর্তমান ভ্যালু ব্যবহার করে নতুন ভ্যালু সেট
    },
    theme: {
        mode: "light",
        color: "#ffffff",
    },
}));

replaceStore: পুরো স্টোর রিপ্লেস বা প্রতিস্থাপন করা

সাধারণত setMultiple নতুন ডেটাকে পুরনো ডেটার সাথে মার্জ করে। কিন্তু যদি আপনার এমন পরিস্থিতির সৃষ্টি হয় যেখানে স্টোরের পুরনো ডেটা সম্পূর্ণ মুছে ফেলে নতুন ডেটা দিয়ে স্টোরটিকে প্রতিস্থাপন করতে হবে, তখন আপনি setMultiple এর দ্বিতীয় আর্গুমেন্ট হিসেবে true (replaceStore ফ্ল্যাগ) পাস করতে পারেন।

// Immer ছাড়া, অবজেক্ট দিয়ে পুরো স্টোর রিপ্লেস করা
store.setMultiple(
    {
        auth: { isLoggedin: false },
        theme: { mode: "light", color: "#ffffff" },
    },
    true, // replaceStore = true
);

// Immer ছাড়া, ফাংশনাল অ্যাপ্রোচ দিয়ে পুরো স্টোর রিপ্লেস করা
store.setMultiple(
    (state) => ({
        auth: { isLoggedin: !state.auth.isLoggedin },
        theme: { mode: "light", color: "#ffffff" },
    }),
    true, // replaceStore = true
);

Immer র‍্যাপড স্টোরে setMultiple ব্যবহারের সময় আপনি চাইলে সরাসরি draft মিউটেট করার বদলে একটি সম্পূর্ণ নতুন অবজেক্ট রিটার্ন করে এবং দ্বিতীয় আর্গুমেন্টে true (replaceStore) পাস করে পুরো স্টোরটিকে সম্পূর্ণরূপে প্রতিস্থাপন (replace) করতে পারেন। এই পদ্ধতির সবচেয়ে বড় সুবিধা হলো, নতুন অবজেক্ট তৈরির সময় আপনি draft থেকে খুব সহজেই বর্তমান স্টেট (যেমন: draft.auth.isLoggedin) পড়ে তার ওপর ভিত্তি করে লজিক লিখতে পারবেন।

store.setMultiple(
    (draft) => ({
        auth: { isLoggedin: !draft.auth.isLoggedin }, // ড্রাফট থেকে বর্তমান স্টেট পড়া হচ্ছে
        theme: { mode: "light", color: "#ffffff" },
    }),
    true, // replaceStore = true
);

সতর্কতা: যখন আপনি replaceStore = true ব্যবহার করবেন, তখন একটি বিশেষ সতর্কতা অবলম্বন করা অত্যন্ত জরুরি। এই ফ্ল্যাগটি দেওয়ার অর্থ হলো আপনি যে নতুন অবজেক্টটি রিটার্ন করবেন, সেটিই হবে আপনার সম্পূর্ণ নতুন স্টোর। আপনি যদি সেখানে স্টোরের পুরোনো কোনো কি (যেমন: cart বা settings) উল্লেখ করতে ভুলে যান, তবে সেগুলো স্টোর থেকে চিরতরে হারিয়ে যাবে। তাই পুরো স্টোরের সব কি নিশ্চিত করে তবেই রিপ্লেস অপশন ব্যবহার করা উচিত।

আর যদি আপনি ফাংশনাল অ্যাপ্রোচে ম্যানুয়ালি ...state স্প্রেড করে দেন, তবে replaceStore = true দেওয়া সত্ত্বেও কার্যত সব পুরোনো কি ধরে রাখা হয়, যা রিপ্লেস করার মূল উদ্দেশ্যকে ব্যাহত করতে পারে।

// স্প্রেড করার কারণে রিপ্লেস ফ্ল্যাগ কার্যত অকেজো হয়ে যাচ্ছে
store.setMultiple(
    (state) => ({
        ...state, // সব পুরোনো কি ধরে রাখা হচ্ছে
        auth: { isLoggedin: false }, // শুধু auth ওভাররাইড হচ্ছে
    }),
    true,
);

আলাদা স্টোর বনাম setMultiple এর সঠিক ব্যবহার

অ্যাপ্লিকেশনের স্টেট আর্কিটেকচার অনুযায়ী আলাদা স্টোর আপডেট করা এবং setMultiple ব্যবহারের পরিস্থিতি সম্পূর্ণ ভিন্ন হয়। যদি আপনার অ্যাপ্লিকেশনে এমন কোনো হ্যান্ডলার বা ফাংশন থাকে, যেখান থেকে আপনাকে সম্পূর্ণ আলাদা আলাদা স্টোরের (যেমন: authStore এবং themeStore) ডেটা একসাথে আপডেট করতে হয়, তবে সেখানে setMultiple ব্যবহার করার কোনো সুযোগ নেই। এই ধরনের পরিস্থিতিতে আপনাকে প্রতিটি স্টোরের জন্য আলাদা আলাদাভাবেই setState কল করতে হবে।

// authStore এবং themeStore দুটি সম্পূর্ণ আলাদা স্টোর হওয়ার কারণে আলাদা setState কল
function handleReset() {
    authStore.setState(
        (keys) => keys.auth,
        (curr) => ({ ...curr, isLoggedin: false }),
    );
    themeStore.setState(
        (keys) => keys.theme,
        (curr) => (curr === "light" ? "dark" : "light"),
    );
}

অন্যদিকে, যদি আপনার প্রয়োজনীয় ডেটাগুলো একই স্টোরের (যেমন: গ্লোবাল store) একাধিক ভিন্ন ভিন্ন কি (key)-তে অবস্থান করে, তবে সেখানে কখনোই বারবার setState কল করা উচিত নয়। এ ধরনের ক্ষেত্রে সবসময় setMultiple ব্যবহার করবেন। এর ফলে রিয়্যাক্ট একাধিক রেন্ডারের বদলে মাত্র একটি আপডেট সাইকেলেই স্টোরের সব পরিবর্তন একসাথে সম্পন্ন করে ফেলে, যা অ্যাপ্লিকেশনের পারফরম্যান্সকে অত্যন্ত দ্রুত এবং অপটিমাইজড রাখে।

// একই স্টোরের auth এবং theme একসাথে আপডেট করার জন্য setMultiple এর ব্যবহার
function handleReset() {
    store.setMultiple((draft) => {
        draft.auth.isLoggedin = false;
        draft.theme.mode = "light";
    });
}

কোন পরিস্থিতিতে কোন অ্যাপ্রোচ (Approach) ব্যবহার করবেন?

NexusStore-এর বিভিন্ন মেথড এবং অপটিমাইজেশন টেকনিকগুলো কখন এবং কোথায় ব্যবহার করলে সবচেয়ে ভালো ফলাফল পাওয়া যাবে, তা পরিষ্কারভাবে বোঝা একজন ডেভেলপারের জন্য অত্যন্ত জরুরি। আপনার অ্যাপ্লিকেশনের পরিস্থিতির ওপর ভিত্তি করে সঠিক মেথডটি সহজে নির্বাচন করার সুবিধার্থে নিচে একটি নির্দেশিকা বা চিট-শিট (cheat sheet) দেওয়া হলো:

পরিস্থিতি সঠিক অ্যাপ্রোচ বা পদ্ধতি
প্রিমিটিভ ভ্যালু আপডেট (String, Number, Boolean) setState এর সাথে সরাসরি ভ্যালু (Direct Value) পাস করা
বর্তমান ভ্যালুর ওপর নির্ভর করে আপডেট setState এর সাথে ফাংশনাল আপডেট (Functional Update) ব্যবহার করা
নেস্টেড অবজেক্ট/অ্যারে আপডেট (Immer র‍্যাপড ছাড়া স্টোর) setState এর ভেতর সরাসরি produce((draft) => {...}) ব্যবহার করা
নেস্টেড অবজেক্ট/অ্যারে আপডেট (Immer র‍্যাপড স্টোর) setState এর ভেতর সরাসরি ড্রাফট ফাংশন (draft) => {...} ব্যবহার করা
একই স্টোরের একাধিক কি (Key) একসাথে আপডেট শুধুমাত্র setMultiple ব্যবহার করা
পুরো স্টোর নতুন ডেটা দিয়ে রিপ্লেস বা প্রতিস্থাপন setMultiple-এর দ্বিতীয় আর্গুমেন্টে true (replaceStore) পাস করা
আলাদা স্টোরের ডেটা একই হ্যান্ডলারে আপডেট আলাদা আলাদা স্টোরের setState কল করা ছাড়া অন্য কোনো উপায় নেই

সারসংক্ষেপ

NexusStore-এর মাধ্যমে একটি অত্যন্ত দ্রুত, স্কেলেবল এবং বাগ-ফ্রি রিয়্যাক্ট অ্যাপ্লিকেশন তৈরি করার মূল চাবিকাঠি হলো এর সঠিক নিয়ম এবং বেস্ট প্র্যাকটিসগুলো নিখুঁতভাবে মেনে চলা। শুরুতেই মনে রাখতে হবে, createStore দিয়ে সর্বদা কম্পোনেন্টের বাইরে গ্লোবালি স্টোর তৈরি করতে হবে। স্টোর থেকে ডেটা পড়ার সময় অপ্রয়োজনীয় রি-রেন্ডার এড়াতে useStore হুকের সাথে সবসময় কম্পোনেন্টের বাইরে সংজ্ঞায়িত অথবা useCallback দিয়ে তৈরি করা একটি স্ট্যাবল সিলেক্টর ফাংশন ব্যবহার করা উচিত। ডেটা আপডেটের ক্ষেত্রে setState মেথডটি শুধুমাত্র টপ-লেভেল কি-এর ওপর কাজ করে; তাই জটিল এবং নেস্টেড অবজেক্ট বা অ্যারে অত্যন্ত সহজে হ্যান্ডেল করার জন্য স্টোর তৈরির সময়ই সেটিকে immer() দিয়ে র‍্যাপ করে নেওয়া হলো একজন প্রফেশনাল ডেভেলপারের সবচেয়ে স্মার্ট সিদ্ধান্ত।

অ্যাপ্লিকেশনের স্টেট আর্কিটেকচার ডিজাইনের ক্ষেত্রে, সম্পর্কহীন ডেটার জন্য আলাদা আলাদা স্টোর ব্যবহার করা উচিত। কিন্তু যখন একই স্টোরের একাধিক ডেটা একসাথে পরিবর্তন করার প্রয়োজন হয়, তখন বারবার setState কল না করে সবসময় setMultiple ব্যবহার করতে হবে। এর ফলে রিয়্যাক্টের মাত্র একটি আপডেট সাইকেলেই সমস্ত পরিবর্তন সম্পন্ন হয়ে যায়, যা অ্যাপ্লিকেশনের পারফরম্যান্স বহুগুণ বাড়িয়ে দেয়। পরিশেষে, আপনার প্রয়োজনীয়তার ওপর ভিত্তি করে কখন সরাসরি ভ্যালু পাস করতে হবে, কখন ফাংশনাল আপডেট প্রয়োজন এবং কখন replaceStore দিয়ে পুরো স্টোর রিপ্লেস করতে হবে—এই বিষয়গুলোর সঠিক এবং সময়োপযোগী ব্যবহারের মাধ্যমেই আপনি NexusStore-এর শতভাগ অপটিমাইজেশন এবং সেরা আউটপুট নিশ্চিত করতে পারবেন।

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors