একটি ফিডে বিশটি আইটেম থাকলে সবকিছু নিখুঁত মনে হয়। স্ক্রলিং একদম মসৃণ হয়। আপনার ক্লায়েন্ট খুশি। তারপর আপনি যখন প্রোডাকশনে রিলিজ করেন, ডেটা আসে এবং হঠাৎ আপনি দুই হাজারটি রো (row) দেখতে পান। UI আটকে যেতে শুরু করে। মেমরি বাড়তে বাড়তে একসময় OS অ্যাপটি বন্ধ করে দেয়। নিরুপায় হয়ে কিছু ডেভেলপার সবকিছুকে একটি ScrollView-এর মধ্যে মুড়িয়ে দিয়ে কাজ শেষ করে দেন। এই সিদ্ধান্তটি সাধারণত একটি সমস্যার সমাধানের বদলে তিনটি নতুন বাগ (bug) তৈরি করে।

লিস্ট হলো পারফরম্যান্সের সেই বাধা (bottleneck) যা নির্ধারণ করে ব্যবহারকারীরা আপনার React Native অ্যাপটিকে কীভাবে দেখবে। লিস্ট ঠিকঠাক থাকলে অ্যাপটি নেটিভ অ্যাপের মতো মনে হবে। আর যদি ভুল হয়, তবে সবচেয়ে সুন্দর স্ক্রিনটিও ধীরগতির হয়ে যাবে। এর মূল কারণ সাধারণত আপনি যে কম্পোনেন্টটি বেছে নিয়েছেন এবং JavaScript ও UI থ্রেডকে আপনি যে কাজ করতে বলছেন, তার মধ্যে অমিল থাকা। React Native দুটি ট্র্যাকে চলে। আপনার লজিক থাকে JS থ্রেডে, আর পেইন্টিং বা রেন্ডারিং ঘটে নেটিভ UI থ্রেডে। যখন আপনি ভুলভাবে একটি বিশাল লিস্ট রেন্ডার করেন, তখন উভয় থ্রেডই লেআউট ক্যালকুলেশন, রি-রেন্ডার এবং মেমরি অ্যালোকেশনের চাপে ডুবে যেতে থাকে। এর ফলে ফ্রেম ড্রপ হয়, স্ক্রিন সাদা হয়ে ফ্ল্যাশ করে এবং শেষ পর্যন্ত অ্যাপ ক্র্যাশ করে।

সঠিক টুল বেছে নিন

একটি লিস্ট কম্পোনেন্ট বেছে নেওয়া উচিত একটি সুচিন্তিত আর্কিটেকচারাল সিদ্ধান্ত হিসেবে, কোনো তাৎক্ষণিক প্রতিক্রিয়া হিসেবে নয়।

ScrollView হলো সবচেয়ে সহজ বিকল্প। এটি আপনি যে চাইল্ড কম্পোনেন্টগুলো দেবেন, সেগুলোর প্রতিটিকেই সাথে সাথে মেমরিতে মাউন্ট করে এবং পুরো স্তূপটি নেটিভ স্ক্রল ইঞ্জিনের কাছে হস্তান্তর করে। সেটিংস স্ক্রিন, লগইন ফর্ম বা দশটি সেকশন বিশিষ্ট একটি স্ট্যাটিক প্রোডাক্ট ডিটেইল পেজের মতো ছোট এবং নির্দিষ্ট কন্টেন্টের জন্য এটি একদম উপযুক্ত। এটি ব্যবহার করা সহজ এবং স্টাইল করাও সহজ। সমস্যা হলো এতে কোনো ভার্চুয়ালাইজেশন (virtualization) নেই। আপনি যদি এতে দুই হাজারটি আইটেম দেন, তবে এটি বাধ্য হয়েই দুই হাজারটি নেটিভ ভিউ তৈরি করবে। বড় বা ডাইনামিক ডেটাসেটের জন্য ScrollView ব্যবহার করবেন না। এটিকে একটি ফ্রেম করা পোস্টারের মতো ভাবুন, লাইব্রেরির শেলফের মতো নয়।

FlatList হলো দীর্ঘ এবং অভিন্ন ফিডের জন্য প্রধান হাতিয়ার। এটি কন্টেন্টকে ভার্চুয়ালাইজ করে, যার মানে হলো এটি শুধুমাত্র সেই রো (row) গুলোকেই মাউন্ট করে যা বর্তমানে দৃশ্যমান বা ভিউপোর্টের কাছাকাছি আছে। ব্যবহারকারী যখন স্ক্রল করেন, FlatList স্ক্রিনের বাইরে চলে যাওয়া সেলগুলোকে আনমাউন্ট করে এবং নতুন ডেটার জন্য সেগুলো রিসাইকেল করে। এর ফলে আপনার অ্যারে (array) যত বড়ই হোক না কেন, মেমরি স্থিতিশীল থাকে। আপনি যদি একটি সোশ্যাল টাইমলাইন, নোটিফিকেশন সেন্টার বা একই ধরণের কার্ডের কোনো অবিরাম স্ক্রলিং কালেকশন তৈরি করেন, তবে FlatList হলো ডিফল্ট সঠিক পছন্দ।

SectionList হলো একটি সুসংগঠিত FlatList। যখন আপনার ডেটা গ্রুপ আকারে আসে, যেমন বর্ণানুক্রমিকভাবে সাজানো একটি অ্যাড্রেস বুক, তারিখ অনুযায়ী বিভক্ত একটি ওয়ার্কআউট লগ, বা মাস অনুযায়ী সাজানো একটি ইনভয়েস লিস্ট, তখন এটি ব্যবহার করুন। এটি স্টিকি সেকশন হেডার রেন্ডার করে এবং আপনার হয়ে গ্রুপিং লজিক সামলায়। এর ভেতরে এটি FlatList-এর মতোই ভার্চুয়ালাইজেশন ইঞ্জিন ব্যবহার করে, তাই আপনি একই মেমরি সুবিধা পাবেন এবং সাথে শিরোনামযুক্ত বিভাজনের বাড়তি সুবিধা পাবেন।

যখন আপনার ডিভাইসের প্রতিটি ফ্রেমকে সর্বোচ্চ কাজে লাগানোর প্রয়োজন হয়, তখন FlashList কাজে লাগে। RecyclerListView ইকোসিস্টেমের ওপর ভিত্তি করে তৈরি এই লিস্টটি FlatList-এর চেয়েও বেশি আগ্রাসীভাবে ভিউ রিসাইকেল করে এবং মিড-রেঞ্জ হার্ডওয়্যারেও প্রতি সেকেন্ডে ৬০টি ফ্রেম (sixty frames per second) বজায় রাখার লক্ষ্য রাখে। আপনি যদি একটি উচ্চ-ভলিউম চ্যাট ইন্টারফেস, দ্রুত স্ক্রলিং করা যায় এমন একটি প্রোডাক্ট ক্যাটালগ, বা এমন কোনো স্ক্রিন তৈরি করেন যেখানে মসৃণতা একটি প্রতিযোগিতামূলক সুবিধা, তবে FlashList-এর জন্য অতিরিক্ত ডিপেন্ডেন্সি নেওয়া সার্থক হবে। এটি প্রতিটি স্ক্রিনের জন্য প্রয়োজনীয় নয়, তবে যে ফিডগুলো অ্যাপের মূল অভিজ্ঞতা নির্ধারণ করে, সেগুলোর ক্ষেত্রে পারফরম্যান্সের পার্থক্যটি লক্ষণীয়।

সাধারণ পারফরম্যান্স নষ্টকারী বিষয়সমূহ

যখন একটি লিস্ট ধীর হয়ে যায়, তখন সাধারণত তিনটি কারণ দায়ী থাকে।

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

প্রতি ফ্রেমের জন্য অতিরিক্ত কাজ করা স্ক্রল করার সময় ঝাকুনি বা স্টাটারিং (stuttering) হিসেবে প্রকাশ পায়। অ্যানিমেশন মসৃণ রাখার জন্য আপনার কাছে প্রতি ফ্রেমের জন্য প্রায় ১৬ মিলিসেকেন্ডের একটি বাজেট থাকে। যদি একটি রো কম্পোনেন্ট ব্যয়বহুল ক্যালকুলেশন করে, তাৎক্ষণিকভাবে ডেট পার্স (parse) করে, অথবা রেন্ডারের ভেতরে গভীর অবজেক্ট তুলনা (deep object comparison) করে, তবে আপনি সেই বাজেট অতিক্রম করবেন। ফলে UI থ্রেড ফ্রেম ড্রপ করে এবং ব্যবহারকারী একটি ঝাকুনি অনুভব করেন।

অতিরিক্ত মেমরি ব্যবহার হলো একটি নীরব ঘাতক। প্রতিটি নেটিভ ভিউ RAM খরচ করে। বড় এবং অপ্টিমাইজ না করা ছবি, প্রতিটি কার্ডে ড্রপ শ্যাডো, অথবা নেস্টেড টাচেবল (nested touchables) যোগ করলে মেমরির ব্যবহার বহুগুণ বেড়ে যায়। iOS-এ সিস্টেম কোনো সতর্কতা ছাড়াই আপনার অ্যাপটি বন্ধ করে দিতে পারে। অন্যদিকে Android-এ ব্যবহারকারী ল্যাগ বাড়তে বাড়তে দেখেন যতক্ষণ না অ্যাপটি ব্যবহার অনুপযোগী হয়ে পড়ে।

অপ্টিমাইজেশন চেকলিস্ট

ছোট কিছু কৌশলগত অভ্যাস একটি সাধারণ কার্যকরী লিস্ট এবং একটি অত্যন্ত দ্রুতগতির লিস্টের মধ্যে পার্থক্য তৈরি করে।

স্থায়ী (stable) key ব্যবহার করুন। আপনার ডেটাসেট থেকে একটি প্রকৃত আইডেন্টিফায়ার সর্বদা key প্রপে পাস করুন। কখনোই অ্যারে ইনডেক্স (array index) ব্যবহার করবেন না। যদি আপনার লিস্টটি পুনরায় সাজানো (reorder), ফিল্টার বা নতুন আইটেম যোগ করে, তবে ইনডেক্স-ভিত্তিক key ব্যবহার করলে React ভুল ডেটাকে ভুল রিসাইকেল করা কম্পোনেন্টের সাথে যুক্ত করে ফেলে। এই ভুলের কারণে অপ্রয়োজনীয় unmount, স্টেট মিসম্যাচ এবং ক্রমাগত re-render ঘটে। একটি সঠিক ID React-কে স্পষ্টভাবে বলে দেয় কোন রো (row) কোথায় সরে গেছে।

রো (rows) মেমোইজ (Memoize) করুন। আপনার রো কম্পোনেন্টটিকে React.memo দিয়ে র‍্যাপ (wrap) করুন যাতে এটি শুধুমাত্র তখনই re-render হয় যখন এর props আসলে পরিবর্তিত হয়। এই সুরক্ষা ছাড়া, প্যারেন্ট স্টেটের যেকোনো আপডেট দৃশ্যমান প্রতিটি রো-তে রেন্ডার পাস ট্রিগার করতে পারে, এমনকি যদি তাদের ডেটা একইও হয়। একটি দীর্ঘ লিস্টে এই অপচয়িত সাইকেলগুলো খুব দ্রুত জমে যায়।

renderItem স্থিতিশীল রাখুন। প্রতিবার প্যারেন্ট রেন্ডারের সময় renderItem প্রপে সরাসরি নতুন ফাংশন ডিফাইন করা এড়িয়ে চলুন। renderItem={({ item }) => <Row data={item} />} এর মতো একটি ইনলাইন অ্যারো ফাংশন প্রতিবার প্যারেন্ট আপডেট হওয়ার সময় একটি নতুন রেফারেন্স তৈরি করে। FlatList তখন একটি পরিবর্তিত প্রপ হিসেবে এটিকে দেখে এবং অপ্রয়োজনীয়ভাবে রো রিসাইকেল করে। রেন্ডার ফাংশনটি কম্পোনেন্টের বাইরে ডিফাইন করুন অথবা useCallback দিয়ে মেমোইজ করুন যাতে রেফারেন্সটি স্থিতিশীল থাকে।

সম্ভব হলে getItemLayout ব্যবহার করুন। যদি আপনার রো-গুলোর উচ্চতা নির্দিষ্ট বা অনুমানযোগ্য হয়, তবে FlatList-কে সেটি স্পষ্টভাবে জানিয়ে দিন। এই প্রপটি লিস্টকে ব্যয়বহুল নেটিভ মেজারমেন্ট কল (native measurement calls) এড়িয়ে চলতে সাহায্য করে। মাউন্ট হওয়ার পর প্রতিটি সেল পরিমাপ করার পরিবর্তে, লিস্টটি গাণিতিকভাবে পজিশন গণনা করে। শত শত বা হাজার হাজার আইটেম বিশিষ্ট লিস্টের ক্ষেত্রে এর পার্থক্য বিশেষভাবে স্পষ্ট হয়, যেখানে onLayout-এর অতিরিক্ত কাজ JS থ্রেডকে স্লো করে দিতে পারে।

ছবিগুলোকে আগ্রাসীভাবে অপ্টিমাইজ করুন। অনিয়ন্ত্রিত (unbounded) ছবি লিস্টের জন্য বিষের মতো। সর্বদা স্পষ্ট প্রস্থ (width) এবং উচ্চতা (height) সেট করুন যাতে ইমেজ ডিকোড হওয়ার আগেই নেটিভ লেয়ার জায়গাটি সংরক্ষণ করে রাখতে পারে। রিমোট ইমেজের জন্য Expo Image বা সমমানের কোনো ক্যাশিং লাইব্রেরি ব্যবহার করুন যা মেমরি ক্যাশিং, ডিস্ক পারসিস্টেন্স এবং ফরম্যাট অপ্টিমাইজেশন পরিচালনা করতে পারে। ডিফল্ট React Native Image কম্পোনেন্ট প্রোটোটাইপের জন্য কাজ করলেও, প্রোডাকশন ফিডের জন্য মেমরি এবং লোডিং স্টেটের ওপর আরও নিয়ন্ত্রণের প্রয়োজন হয়।

স্ক্রল কন্টেইনার নেস্টিং করা এড়িয়ে চলুন। একটি ভার্টিক্যাল ScrollView-এর ভেতরে কখনোই একটি ভার্টিক্যাল FlatList রাখবেন না। প্যারেন্ট ScrollView সমস্ত স্ক্রল ইভেন্ট ক্যাপচার করে এবং চাইল্ড FlatList-এর ভিউপোর্ট (viewport) পরিমাপ করার ক্ষমতাকে ব্যাহত করে। ভার্চুয়ালাইজেশন (virtualization) কাজ করে না কারণ FlatList আর বুঝতে পারে না কোন রো-গুলো দৃশ্যমান হওয়া উচিত। ফলাফল হিসেবে প্রতিটি রো-ই মাউন্ট হয়, যা ভার্চুয়ালাইজেশনের পুরো উদ্দেশ্যকেই ব্যর্থ করে দেয়। যদি লিস্টের উপরে একটি হেডার প্রয়োজন হয়, তবে FlatList-এর নিজস্ব ListHeaderComponent প্রপ ব্যবহার করুন। যদি জটিল স্টিকি (sticky) আচরণ প্রয়োজন হয়, তবে উপযুক্ত হেডার কনফিগারেশনসহ SectionList বা FlashList ব্যবহার করুন।

গোল্ডেন রুল (The Golden Rule)

যদি কন্টেন্ট ছোট এবং সীমিত হয়, তবে ScrollView ব্যবহার করুন। যদি কন্টেন্ট ইউজার-জেনারেটেড ডেটা বা রিমোট প্যাগিনেশনের (remote pagination) মাধ্যমে বাড়তে থাকে, তবে একটি ভার্চুয়ালাইজড লিস্ট ব্যবহার করুন। যখন লিস্টটি অ্যাপের মূল কেন্দ্রবিন্দু হয় এবং ব্যবহারকারীরা দীর্ঘ সময় ধরে স্ক্রল করবেন, তখন FlashList ব্যবহার করুন।

ভুলে যাওয়ার মতো একটি শেষ সত্য হলো—সাধারণ (boring) রো-গুলো দ্রুত স্ক্রল হয়। আপনার রো কম্পোনেন্ট যত হালকা হবে, লিস্ট তত মসৃণ হবে। প্রতিটি রো থেকে নেস্টেড নেভিগেশন, ভারী কম্পিউটেশন এবং অপ্রয়োজনীয় অ্যানিমেশন সরিয়ে ফেলুন। মার্কআপ রাখুন ফ্ল্যাট (flat), লজিক রাখুন হালকা এবং ইমেজের সাইজ ঠিক রাখুন। একটি লিস্ট কী রেন্ডার করছে তার সামগ্রিক ওজনের ওপর ভিত্তি করেই এর কার্যকারিতা নির্ভর করে। প্রতিটি রো-কে হালকা বা 'চিপ' (cheap) রাখুন, তাহলে লিস্টটি সেরা উপায়ে প্রিমিয়াম বা 'এক্সপেনসিভ' (expensive) মনে হবে।