2013 সালে আমি ওয়েব অ্যাপ্লিকেশন বিকাশের জন্য একটি ন্যূনতম সরঞ্জাম তৈরি করার জন্য সেট করেছিলাম। সম্ভবত সেই প্রক্রিয়া থেকে বেরিয়ে আসা সেরা জিনিসটি ছিল gotoB , একটি ক্লায়েন্ট-সাইড, বিশুদ্ধ JS ফ্রন্টএন্ড ফ্রেমওয়ার্ক যা 2k লাইনের কোডে লেখা।
খুব সফল ফ্রন্টএন্ড ফ্রেমওয়ার্কের লেখকদের আকর্ষণীয় নিবন্ধ পড়ার খরগোশের গর্তে যাওয়ার পরে আমি এই নিবন্ধটি লিখতে অনুপ্রাণিত হয়েছিলাম:
এই নিবন্ধগুলি সম্পর্কে যা আমাকে উত্তেজিত করেছিল তা হল তারা যা তৈরি করে তার পিছনের ধারণাগুলি বিবর্তন সম্পর্কে কথা বলে; বাস্তবায়ন হল সেগুলিকে বাস্তব করার একটি উপায়, এবং শুধুমাত্র আলোচিত বৈশিষ্ট্যগুলি হল যেগুলি নিজেরাই ধারণাগুলিকে উপস্থাপন করার জন্য অত্যন্ত প্রয়োজনীয়৷
এখন পর্যন্ত, gotoB থেকে যা বেরিয়ে এসেছে তার সবচেয়ে আকর্ষণীয় দিক হল এটি নির্মাণের চ্যালেঞ্জ মোকাবেলা করার ফলে বিকশিত ধারণাগুলি। যে আমি এখানে কভার করতে চান কি.
যেহেতু আমি স্ক্র্যাচ থেকে ফ্রেমওয়ার্ক তৈরি করেছি, এবং আমি ন্যূনতমতা এবং অভ্যন্তরীণ সামঞ্জস্য উভয়ই অর্জন করার চেষ্টা করছিলাম, আমি চারটি সমস্যা এমনভাবে সমাধান করেছি যা আমি মনে করি যে বেশিরভাগ ফ্রেমওয়ার্ক একই সমস্যাগুলি সমাধান করার উপায় থেকে আলাদা।
এই চারটি ধারণা আমি এখন আপনাদের সাথে শেয়ার করতে চাই। আমি আপনাকে আমার সরঞ্জামগুলি ব্যবহার করার জন্য সন্তুষ্ট করার জন্য এটি করি না (যদিও আপনাকে স্বাগত জানাই!), বরং আশা করি যে আপনি নিজেরাই ধারণাগুলিতে আগ্রহী হতে পারেন৷
যেকোনো ওয়েব অ্যাপ্লিকেশনকে অ্যাপ্লিকেশনের অবস্থার উপর ভিত্তি করে ফ্লাইতে মার্কআপ (HTML) তৈরি করতে হবে।
এটি একটি উদাহরণের মাধ্যমে সবচেয়ে ভালোভাবে ব্যাখ্যা করা হয়েছে: একটি অতি-সাধারণ টোডো তালিকা অ্যাপ্লিকেশনে, রাজ্যটি টোডোগুলির একটি তালিকা হতে পারে: ['Item 1', 'Item 2']
। যেহেতু আপনি একটি অ্যাপ্লিকেশন লিখছেন (একটি স্ট্যাটিক পৃষ্ঠার বিপরীতে), টোডোর তালিকা অবশ্যই পরিবর্তন করতে সক্ষম হবে।
রাজ্যের পরিবর্তনের কারণে, আপনার অ্যাপ্লিকেশনের UI তৈরি করে এমন HTML রাজ্যের সাথে পরিবর্তন করতে হবে। উদাহরণস্বরূপ, আপনার কাজগুলি প্রদর্শন করতে, আপনি নিম্নলিখিত HTML ব্যবহার করতে পারেন:
<ul> <li>Item 1</li> <li>Item 2</li> </ul>
যদি রাষ্ট্র পরিবর্তন হয় এবং একটি তৃতীয় আইটেম যোগ করা হয়, তাহলে আপনার অবস্থা এখন এইরকম দেখাবে: ['Item 1', 'Item 2', 'Item 3']
; তারপর, আপনার HTML এর মত হওয়া উচিত:
<ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul>
অ্যাপ্লিকেশনের অবস্থার উপর ভিত্তি করে এইচটিএমএল তৈরি করার সমস্যাটি সাধারণত একটি টেমপ্লেটিং ভাষা দিয়ে সমাধান করা হয়, যা ছদ্ম-এইচটিএমএল-এ প্রোগ্রামিং ভাষা গঠন (ভেরিয়েবল, শর্তাবলী এবং লুপ) সন্নিবেশিত করে যা প্রকৃত এইচটিএমএলে প্রসারিত হয়।
উদাহরণস্বরূপ, এখানে দুটি উপায় রয়েছে যাতে এটি বিভিন্ন টেমপ্লেটিং সরঞ্জামগুলিতে করা যেতে পারে:
// Assume that `todos` is defined and equal to ['Item 1', 'Item 2', 'Item 3'] // Moustache <ul> {{#todos}} <li>{{.}}</li> {{/todos}} </ul> // JSX <ul> {todos.map((item, index) => ( <li key={index}>{item}</li> ))} </ul>
আমি কখনই এই সিনট্যাক্সগুলি পছন্দ করিনি যা HTML-এ যুক্তি এনেছিল। টেমপ্লেটিং এর জন্য প্রয়োজনীয় প্রোগ্রামিং বুঝতে পেরে এবং এর জন্য আলাদা সিনট্যাক্স থাকা এড়াতে চেয়ে, আমি অবজেক্ট লিটারাল ব্যবহার করে এইচটিএমএলকে js-এ আনার সিদ্ধান্ত নিয়েছি। সুতরাং, আমি কেবল আমার HTML কে অবজেক্ট লিটারেল হিসাবে মডেল করতে পারি:
['ul', [ ['li', 'Item 1'], ['li', 'Item 2'], ['li', 'Item 3'], ]]
যদি আমি তালিকা তৈরি করতে পুনরাবৃত্তি ব্যবহার করতে চাই, আমি কেবল লিখতে পারি:
['ul', items.map ((item) => ['li', item])]
এবং তারপর একটি ফাংশন ব্যবহার করুন যা এই বস্তুটিকে আক্ষরিকভাবে HTML এ রূপান্তর করবে। এইভাবে, সমস্ত টেমপ্লেটিং JS-এ করা যেতে পারে, কোনো টেমপ্লেটিং ভাষা বা ট্রান্সপিলেশন ছাড়াই। আমি এই অ্যারেগুলি বর্ণনা করতে নাম লিথ ব্যবহার করি যা HTML প্রতিনিধিত্ব করে।
আমার জানামতে, অন্য কোন JS ফ্রেমওয়ার্ক এইভাবে টেমপ্লেটিং করে না। আমি কিছু খনন করেছি এবং JSONML পেয়েছি, যা JSON অবজেক্টে এইচটিএমএল প্রতিনিধিত্ব করার জন্য প্রায় একই কাঠামো ব্যবহার করে (যা প্রায় জেএস অবজেক্ট লিটারেলের মতো), কিন্তু এটির চারপাশে নির্মিত কোনও কাঠামো খুঁজে পাইনি।
Mithril এবং Hyperapp আমি যে পদ্ধতি ব্যবহার করেছি তার বেশ কাছাকাছি চলে এসেছে, কিন্তু তারা এখনও প্রতিটি উপাদানের জন্য ফাংশন কল ব্যবহার করে।
// Mithril m("ul", [ m("li", "Item 1"), m("li", "Item 2") ]) // hyperapp h("ul", [ h("li", "Item 1"), h("li", "Item 2") ])
অবজেক্ট লিটারেল ব্যবহার করার পদ্ধতিটি HTML এর জন্য ভাল কাজ করেছে, তাই আমি এটিকে CSS-এ প্রসারিত করেছি এবং এখন অবজেক্ট লিটারালের মাধ্যমেও আমার সমস্ত CSS তৈরি করেছি।
যদি কোনো কারণে আপনি এমন পরিবেশে থাকেন যেখানে আপনি JSX ট্রান্সপিল করতে পারবেন না বা একটি টেমপ্লেটিং ভাষা ব্যবহার করতে পারবেন না এবং আপনি স্ট্রিংগুলিকে সংযুক্ত করতে চান না, আপনি পরিবর্তে এই পদ্ধতিটি ব্যবহার করতে পারেন।
আমি নিশ্চিত নই যে Mithril/Hyperapp পদ্ধতি আমার চেয়ে ভালো কিনা; আমি দেখতে পাই যে লিথের প্রতিনিধিত্বকারী দীর্ঘ অবজেক্ট লিটারেল লেখার সময়, আমি কখনও কখনও কোথাও একটি কমা ভুলে যাই এবং এটি কখনও কখনও খুঁজে পাওয়া কঠিন হতে পারে। তা ছাড়া, সত্যিই কোন অভিযোগ নেই। এবং আমি এই সত্যটি পছন্দ করি যে এইচটিএমএল এর উপস্থাপনা উভয়ই 1) ডেটা এবং 2) জেএস-এ। এই উপস্থাপনাটি আসলে একটি ভার্চুয়াল DOM হিসাবে কাজ করতে পারে, যেমনটি আমরা দেখতে পাব যখন আমরা Idea #4 এ পৌঁছাব।
বোনাস বিশদ: আপনি যদি অবজেক্ট লিটারাল থেকে এইচটিএমএল তৈরি করতে চান তবে আপনাকে কেবল নিম্নলিখিত দুটি সমস্যা সমাধান করতে হবে:
আমি কখনই উপাদান পছন্দ করিনি। উপাদানগুলির চারপাশে একটি অ্যাপ্লিকেশন গঠনের জন্য উপাদানটির ভিতরের উপাদানগুলির সাথে সম্পর্কিত ডেটা স্থাপন করা প্রয়োজন। এটি অ্যাপ্লিকেশনের অন্যান্য অংশের সাথে সেই ডেটা ভাগ করা কঠিন বা এমনকি অসম্ভব করে তোলে।
প্রতিটি প্রকল্পে আমি কাজ করেছি, আমি দেখেছি যে একে অপরের থেকে বেশ দূরে থাকা উপাদানগুলির মধ্যে ভাগ করার জন্য আমার সবসময় অ্যাপ্লিকেশন স্টেটের কিছু অংশের প্রয়োজন হয়। একটি সাধারণ উদাহরণ হল ব্যবহারকারীর নাম: আপনার অ্যাকাউন্ট বিভাগে এবং শিরোনামে এটির প্রয়োজন হতে পারে। সুতরাং ব্যবহারকারীর নামটি কোথায়?
অতএব, আমি প্রাথমিকভাবে একটি সাধারণ ডেটা অবজেক্ট ( {}
) তৈরি করার সিদ্ধান্ত নিয়েছি এবং সেখানে আমার সমস্ত রাজ্য স্টাফ করব। আমি এটাকে দোকান বলেছিলাম। স্টোরটি অ্যাপের সমস্ত অংশের জন্য স্টেট ধারণ করে এবং তাই যেকোন কম্পোনেন্ট ব্যবহার করতে পারে।
এই পদ্ধতিটি 2013-2015 সালে কিছুটা ধর্মবিরোধী ছিল, কিন্তু তারপর থেকে এটি ব্যাপকতা এবং এমনকি আধিপত্য অর্জন করেছে।
আমি যা মনে করি এখনও বেশ অভিনব তা হল আমি দোকানের ভিতরে যে কোনও মান অ্যাক্সেস করতে পাথ ব্যবহার করি। উদাহরণস্বরূপ, যদি দোকান হয়:
{ user: { firstName: 'foo' lastName: 'bar' } }
আমি B.get ('user', 'lastName')
লিখে lastName
অ্যাক্সেস করার (বলতে) একটি পথ ব্যবহার করতে পারি। আপনি দেখতে পাচ্ছেন, ['user', 'lastName']
হল 'bar'
-এর পথ । B.get
হল একটি ফাংশন যা স্টোরে অ্যাক্সেস করে এবং এর একটি নির্দিষ্ট অংশ ফেরত দেয়, যেটি আপনি ফাংশনে পাস করেন তার দ্বারা নির্দেশিত।
উপরের বিপরীতে, প্রতিক্রিয়াশীল বৈশিষ্ট্যগুলি অ্যাক্সেস করার আদর্শ উপায় হল একটি JS ভেরিয়েবলের মাধ্যমে তাদের উল্লেখ করা। যেমন:
// Svelte let { firstName, lastName } = $props(); firstName = 'foo'; lastName = 'bar'; // Knockout const firstName = ko.observable('foo'); const lastName = ko.observable('bar'); // mobx class UserStore { firstName = 'foo'; lastName = 'bar'; constructor() { makeAutoObservable(this); } } const userStore = new UserStore(); // SolidJS const [firstName, setFirstName] = createSignal('foo'); const [lastName, setLastName] = createSignal('bar');
যাইহোক, এর জন্য আপনাকে firstName
এবং lastName
(অথবা userStore
) যেখানে আপনার সেই মানটি প্রয়োজন সেখানে একটি রেফারেন্স রাখতে হবে। আমি যে পদ্ধতিটি ব্যবহার করি তার জন্য আপনার স্টোরে অ্যাক্সেস থাকতে হবে (যা বিশ্বব্যাপী এবং সর্বত্র উপলব্ধ) এবং তাদের জন্য JS ভেরিয়েবলগুলি সংজ্ঞায়িত না করেই আপনাকে এটিতে সূক্ষ্ম-দানাযুক্ত অ্যাক্সেসের অনুমতি দেয়।
Immutable.js এবং ফায়ারবেস রিয়েলটাইম ডেটাবেস আমি যা করেছি তার অনেক কাছাকাছি কিছু করে, যদিও তারা আলাদা বস্তুতে কাজ করছে। কিন্তু আপনি সম্ভাব্যভাবে একটি একক জায়গায় সবকিছু সংরক্ষণ করতে তাদের ব্যবহার করতে পারেন যা দানাদারভাবে ঠিকানাযোগ্য হতে পারে।
// Immutable.js let store = Map({ user: Map({ firstName: 'foo', lastName: 'bar' }) }); const firstName = store.getIn(['user', 'firstName']); // 'foo' // Firebase const db = firebase.database(); db.ref('user').set({ firstName: 'foo', lastName: 'bar' }); db.ref('user/firstName').once('value').then(snapshot => { const firstName = snapshot.val(); // 'foo' });
একটি বিশ্বব্যাপী অ্যাক্সেসযোগ্য স্টোরে আমার ডেটা থাকা যা পাথের মাধ্যমে ধীরে ধীরে অ্যাক্সেস করা যেতে পারে এমন একটি প্যাটার্ন যা আমি অত্যন্ত দরকারী খুঁজে পেয়েছি। যখনই আমি const [count, setCount] = ...
বা এরকম কিছু লিখি, তখন এটা অপ্রয়োজনীয় মনে হয়। আমি জানি যে যখনই আমি এটি অ্যাক্সেস করতে চাই তখনই আমি কেবলমাত্র B.get ('count')
করতে পারি, count
বা setCount
কাছাকাছি ঘোষণা এবং পাস না করে।
যদি আইডিয়া #2 (পাথের মাধ্যমে অ্যাক্সেসযোগ্য একটি গ্লোবাল স্টোর) উপাদানগুলি থেকে ডেটা মুক্ত করে, তাহলে আইডিয়া #3 হল আমি কীভাবে উপাদানগুলি থেকে কোড মুক্ত করেছি। আমার কাছে, এটি এই নিবন্ধের সবচেয়ে আকর্ষণীয় ধারণা। এখানে এটা যায়!
আমাদের রাজ্য হল ডেটা যা, সংজ্ঞা অনুসারে, পরিবর্তনযোগ্য (যারা অপরিবর্তনীয়তা ব্যবহার করে, যুক্তিটি এখনও দাঁড়িয়েছে: আপনি এখনও রাষ্ট্রের সর্বশেষ সংস্করণ পরিবর্তন করতে চান, এমনকি যদি আপনি রাষ্ট্রের পুরানো সংস্করণগুলির স্ন্যাপশট রাখেন)। আমরা কিভাবে রাষ্ট্র পরিবর্তন করব?
আমি ইভেন্ট নিয়ে যাওয়ার সিদ্ধান্ত নিয়েছি। আমার কাছে ইতিমধ্যেই দোকানে যাওয়ার পথ ছিল, তাই একটি ইভেন্ট হতে পারে একটি ক্রিয়াপদ (যেমন set
, add
বা rem
) এবং একটি পথের সংমিশ্রণ। সুতরাং, যদি আমি user.firstName
আপডেট করতে চাই, আমি এরকম কিছু লিখতে পারি:
B.call ('set', ['user', 'firstName'], 'Foo')
এটি অবশ্যই লেখার চেয়ে বেশি শব্দযুক্ত:
user.firstName = 'Foo';
কিন্তু এটি আমাকে কোড লিখতে দেয় যা user.firstName
এ পরিবর্তনের প্রতিক্রিয়া জানাবে । এবং এটি গুরুত্বপূর্ণ ধারণা: একটি UI-তে, বিভিন্ন অংশ রয়েছে যা রাজ্যের বিভিন্ন অংশের উপর নির্ভরশীল। উদাহরণস্বরূপ, আপনার এই নির্ভরতা থাকতে পারে:
user
এবং currentView
এর উপর নির্ভর করেuser
উপর নির্ভর করেitems
উপর নির্ভর করে
আমি যে বড় প্রশ্নের মুখোমুখি হয়েছিলাম তা হল: user
পরিবর্তন হলে আমি কীভাবে শিরোনাম এবং অ্যাকাউন্ট বিভাগ আপডেট করব, কিন্তু items
পরিবর্তন হলে নয়? এবং updateHeader
বা updateAccountSection
এর মত নির্দিষ্ট কল না করে কিভাবে আমি এই নির্ভরতাগুলি পরিচালনা করব? এই ধরণের নির্দিষ্ট কলগুলি "jQuery প্রোগ্রামিং" এর সবচেয়ে অসংযতভাবে উপস্থাপন করে।
আমার কাছে আরও ভাল ধারণার মতো দেখতে ছিল এইরকম কিছু করা:
B.respond ('set', [['user'], ['currentView']], function (user, currentView) { // Update the header }); B.respond ('set', ['user'], function (user) { // Update the account section }); B.respond ('set', ['items'], function (items) { // Update the todo list });
সুতরাং, যদি user
জন্য একটি set
ইভেন্ট কল করা হয়, তবে ইভেন্ট সিস্টেম সেই পরিবর্তনে আগ্রহী সমস্ত ভিউগুলিকে অবহিত করবে (শিরোনাম এবং অ্যাকাউন্ট বিভাগ), অন্য ভিউগুলি (টুডু তালিকা) অব্যহত রেখে। B.respond
হল একটি ফাংশন যা আমি উত্তরদাতাদের নিবন্ধন করতে ব্যবহার করি (যাকে সাধারণত "ইভেন্ট শ্রোতা" বা "প্রতিক্রিয়া" বলা হয়)। মনে রাখবেন যে উত্তরদাতারা বিশ্বব্যাপী এবং কোনো উপাদানের সাথে আবদ্ধ নয়; তবে, তারা শুনছে শুধুমাত্র নির্দিষ্ট পথে ইভেন্ট set
জন্য।
এখন, কিভাবে একটি change
ইভেন্ট প্রথম স্থানে বলা হয়? এইভাবে আমি এটি করেছি:
B.respond ('set', '*', function () { // Assume that `path` is the path on which set was called B.call ('change', path); });
আমি কিছুটা সরলীকরণ করছি, তবে এটি মূলত গোটোবি-তে কীভাবে কাজ করে।
যেটি একটি ইভেন্ট সিস্টেমকে নিছক ফাংশন কলের চেয়ে আরও শক্তিশালী করে তোলে তা হল একটি ইভেন্ট কল 0, 1 বা একাধিক টুকরো কোড কার্যকর করতে পারে, যেখানে একটি ফাংশন কল সর্বদা ঠিক একটি ফাংশনকে কল করে। উপরের উদাহরণে, আপনি যদি B.call ('set', ['user', 'firstName'], 'Foo');
, কোডের দুটি টুকরা কার্যকর করা হয়: যেটি শিরোনাম পরিবর্তন করে এবং যা অ্যাকাউন্টের দৃশ্য পরিবর্তন করে। মনে রাখবেন firstName
আপডেট করার কলটি কে শুনছে তা "পয়সা" করে না। এটি কেবল তার কাজ করে এবং প্রতিক্রিয়াকারীকে পরিবর্তনগুলি নিতে দেয়।
ইভেন্টগুলি এত শক্তিশালী যে, আমার অভিজ্ঞতায়, তারা গণনা করা মানগুলি এবং সেইসাথে প্রতিক্রিয়াগুলিকে প্রতিস্থাপন করতে পারে। অন্য কথায়, এগুলি কোনও অ্যাপ্লিকেশনে ঘটতে হবে এমন কোনও পরিবর্তন প্রকাশ করতে ব্যবহার করা যেতে পারে।
একটি গণনা করা মান একটি ইভেন্ট উত্তরদাতার সাথে প্রকাশ করা যেতে পারে। উদাহরণস্বরূপ, আপনি যদি একটি fullName
গণনা করতে চান এবং আপনি এটি দোকানে ব্যবহার করতে না চান তবে আপনি নিম্নলিখিতগুলি করতে পারেন:
B.respond ('set', 'user', function () { var user = B.get ('user'); var fullName = user.firstName + ' ' + user.lastName; // Do something with `fullName` here. });
একইভাবে, প্রতিক্রিয়া একজন উত্তরদাতার সাথে প্রকাশ করা যেতে পারে। এটি বিবেচনা করুন:
B.respond ('set', 'user', function () { var user = B.get ('user'); var fullName = user.firstName + ' ' + user.lastName; document.getElementById ('header').innerHTML = '<h1>Hello, ' + fullName + '</h1>'; });
আপনি যদি এক মিনিটের জন্য এইচটিএমএল তৈরি করার জন্য স্ট্রিং-এর সংমিশ্রণ-প্ররোচনাকে উপেক্ষা করেন, আপনি উপরে যা দেখছেন তা হল একজন প্রতিক্রিয়াকারী একটি "পার্শ্ব-প্রতিক্রিয়া" (এই ক্ষেত্রে, DOM আপডেট করা)।
(পার্শ্ব দ্রষ্টব্য: একটি ওয়েব অ্যাপ্লিকেশনের প্রেক্ষাপটে একটি পার্শ্ব-প্রতিক্রিয়ার একটি ভাল সংজ্ঞা কি হবে? আমার কাছে, এটি তিনটি বিষয়ের উপর ফুটে উঠেছে: 1) অ্যাপ্লিকেশনের অবস্থার একটি আপডেট; 2) DOM এ পরিবর্তন; 3) একটি AJAX কল পাঠানো)।
আমি খুঁজে পেয়েছি যে DOM আপডেট করে এমন একটি পৃথক জীবনচক্রের সত্যিই কোন প্রয়োজন নেই। gotoB-তে, কিছু রেসপন্ডার ফাংশন আছে যা কিছু হেল্পার ফাংশনের সাহায্যে DOM আপডেট করে। সুতরাং, যখন user
পরিবর্তন হয়, তখন যেকোন প্রতিক্রিয়াকারী (বা আরও স্পষ্টভাবে, দেখুন ফাংশন , যেহেতু আমি উত্তরদাতাদের এই নামটি দিই যেগুলি DOM-এর একটি অংশ আপডেট করার দায়িত্বপ্রাপ্ত) যেটি এটির উপর নির্ভর করে তা কার্যকর হবে, একটি পার্শ্ব প্রতিক্রিয়া তৈরি করবে যা আপডেট করা শেষ করে DOM
আমি ইভেন্ট সিস্টেমটিকে অনুমানযোগ্য করে তুলেছি এটিকে একই ক্রমে এবং এক সময়ে প্রতিক্রিয়াকারী ফাংশনগুলি চালানোর মাধ্যমে। অ্যাসিঙ্ক্রোনাস রেসপন্সাররা এখনও সিঙ্ক্রোনাস হিসাবে চালাতে পারে এবং তাদের "পরে" আসা রেসপন্ডাররা তাদের জন্য অপেক্ষা করবে।
আরও পরিশীলিত নিদর্শন, যেখানে আপনাকে DOM আপডেট না করেই স্টেট আপডেট করতে হবে (সাধারণত পারফরম্যান্সের উদ্দেশ্যে) মিউট ক্রিয়া যোগ করে যোগ করা যেতে পারে, যেমন mset , যা স্টোরকে পরিবর্তন করে কিন্তু কোনো প্রতিক্রিয়াশীলকে ট্রিগার করে না। এছাড়াও, যদি পুনরায় আঁকার পরে আপনার DOM-এ কিছু করার প্রয়োজন হয়, আপনি কেবলমাত্র নিশ্চিত করতে পারেন যে উত্তরদাতার কম অগ্রাধিকার রয়েছে এবং অন্য সমস্ত প্রতিক্রিয়াকারীদের পরে চলে:
B.respond ('set', 'date', {priority: -1000}, function () { var datePicker = document.getElementById ('datepicker'); // Do something with the date picker });
উপরের পদ্ধতি, ক্রিয়া এবং পাথ ব্যবহার করে একটি ইভেন্ট সিস্টেম এবং নির্দিষ্ট ইভেন্ট কলের সাথে মিলে যাওয়া (সম্পাদিত) গ্লোবাল রেসপন্ডারের একটি সেটের আরেকটি সুবিধা রয়েছে: প্রতিটি ইভেন্ট কল একটি তালিকায় রাখা যেতে পারে। আপনি যখন আপনার অ্যাপ্লিকেশন ডিবাগ করছেন তখন আপনি এই তালিকাটি বিশ্লেষণ করতে পারেন এবং রাজ্যে পরিবর্তনগুলি ট্র্যাক করতে পারেন।
একটি ফ্রন্টএন্ডের প্রেক্ষাপটে, ইভেন্ট এবং প্রতিক্রিয়াকারীরা যা অনুমতি দেয় তা এখানে:
এটি তারা (আমার অভিজ্ঞতায়) ছাড়া করার অনুমতি দেয়:
এটা সত্যিই শুধু ইভেন্ট কল এবং প্রতিক্রিয়াকারী, কিছু প্রতিক্রিয়াকারী শুধুমাত্র মতামত নিয়ে উদ্বিগ্ন, এবং অন্যরা অন্যান্য ক্রিয়াকলাপের সাথে উদ্বিগ্ন। ফ্রেমওয়ার্কের সমস্ত অভ্যন্তরীণ শুধুমাত্র ব্যবহারকারীর স্থান ব্যবহার করছে।
gotoB তে এটি কীভাবে কাজ করে সে সম্পর্কে আপনি যদি আগ্রহী হন তবে আপনি এই বিস্তারিত ব্যাখ্যাটি পরীক্ষা করতে পারেন।
দ্বি-মুখী ডেটা বাইন্ডিং এখন বেশ তারিখের শোনাচ্ছে। কিন্তু আপনি যদি 2013-এ একটি টাইম মেশিন নিয়ে যান এবং রাষ্ট্রের পরিবর্তনের সময় DOM পুনরায় আঁকার সমস্যাটি আপনি প্রথম নীতি থেকে মোকাবেলা করেন, তাহলে এর চেয়ে যুক্তিসঙ্গত কী শোনাবে?
প্রকৃতপক্ষে, বিকল্প 2, যা রাজ্য থেকে DOM-এ একমুখী ডেটা প্রবাহ, আরও জটিল, সেইসাথে অদক্ষ বলে মনে হচ্ছে।
চলুন এখন এটিকে খুব স্থির করা যাক: একটি ইন্টারেক্টিভ <input>
বা <textarea>
ফোকাস করার ক্ষেত্রে, আপনাকে প্রতিটি ব্যবহারকারীর কীস্ট্রোকের সাহায্যে DOM-এর অংশগুলি পুনরায় তৈরি করতে হবে! আপনি যদি একমুখী ডেটা প্রবাহ ব্যবহার করেন, ইনপুটের প্রতিটি পরিবর্তন রাজ্যে একটি পরিবর্তন ট্রিগার করে, যা তারপরে <input>
পুনরায় আঁকতে পারে যাতে এটি ঠিক যা হওয়া উচিত তার সাথে মেলে।
এটি DOM আপডেটের জন্য একটি খুব উচ্চ বার সেট করে: সেগুলি দ্রুত হওয়া উচিত এবং ইন্টারেক্টিভ উপাদানগুলির সাথে ব্যবহারকারীর মিথস্ক্রিয়াকে বাধাগ্রস্ত করা উচিত নয়। এটি মোকাবেলা করা একটি সহজ সমস্যা নয়।
এখন, কেন রাজ্য থেকে DOM (JS থেকে HTML) পর্যন্ত একমুখী ডেটা জিতেছে? কারণ এটি সম্পর্কে যুক্তি করা সহজ। যদি অবস্থার পরিবর্তন হয়, তাহলে এই পরিবর্তনটি কোথা থেকে এসেছে তা কোন ব্যাপার না (সার্ভার থেকে ডেটা আনতে একটি AJAX কলব্যাক হতে পারে, একটি ব্যবহারকারীর মিথস্ক্রিয়া হতে পারে, একটি টাইমার হতে পারে)৷ রাষ্ট্র পরিবর্তন হয় (বা বরং, পরিবর্তিত হয় ) একই ভাবে সবসময়। এবং রাষ্ট্র থেকে পরিবর্তন সবসময় DOM এ প্রবাহিত হয়।
সুতরাং, কীভাবে একজন দক্ষ উপায়ে DOM আপডেটগুলি সম্পাদন করতে পারে যা ব্যবহারকারীর মিথস্ক্রিয়াকে বাধা দেয় না? এটি সাধারণত ন্যূনতম পরিমাণ DOM আপডেটগুলি সম্পাদন করার জন্য ফোটে যা কাজটি সম্পন্ন করবে। এটিকে সাধারণত "ডিফিং" বলা হয়, কারণ আপনি পার্থক্যগুলির একটি তালিকা তৈরি করছেন যা আপনাকে একটি পুরানো কাঠামো (বিদ্যমান DOM) নিতে হবে এবং এটিকে একটি নতুন (রাষ্ট্র আপডেট হওয়ার পরে নতুন DOM) রূপান্তর করতে হবে।
আমি যখন 2016 সালের দিকে এই সমস্যাটি নিয়ে কাজ শুরু করি, তখন কী প্রতিক্রিয়া হচ্ছে তা দেখে আমি প্রতারণা করেছি। তারা আমাকে গুরুত্বপূর্ণ অন্তর্দৃষ্টি দিয়েছে যে দুটি গাছের পার্থক্যের জন্য কোন সাধারণীকৃত, লিনিয়ার-পারফরম্যান্স অ্যালগরিদম নেই (DOM হল একটি গাছ)। কিন্তু, কিছু হলে একগুঁয়ে, আমি এখনও ডিফিং সঞ্চালনের জন্য একটি সাধারণ উদ্দেশ্য অ্যালগরিদম চেয়েছিলাম। প্রতিক্রিয়া সম্পর্কে আমি বিশেষভাবে যা অপছন্দ করেছি (অথবা সেই বিষয়ে প্রায় কোনও কাঠামোর) তা হল জিদ যে আপনাকে সংলগ্ন উপাদানগুলির জন্য কীগুলি ব্যবহার করতে হবে:
function MyList() { const items = ['Item 1', 'Item 2', 'Item 3']; return ( <ul> {items.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> ); }
আমার কাছে, key
নির্দেশটি ছিল অতিশয়, কারণ এটির DOM-এর সাথে কিছু করার ছিল না; এটা ফ্রেমওয়ার্ক শুধুমাত্র একটি ইঙ্গিত ছিল.
তারপরে আমি একটি গাছের সমতল সংস্করণগুলিতে একটি পাঠ্য পার্থক্য অ্যালগরিদম চেষ্টা করার কথা ভেবেছিলাম। যদি আমি উভয় গাছকে চ্যাপ্টা করি (আমার কাছে থাকা DOM-এর পুরানো অংশ এবং DOM-এর নতুন অংশ যা আমি এটির সাথে প্রতিস্থাপন করতে চেয়েছিলাম) এবং এটিতে একটি diff
গণনা করি (সম্পাদনার একটি সর্বনিম্ন সেট), যাতে আমি সেখান থেকে যেতে পারি পুরাতন থেকে নতুন একটি ছোট সংখ্যক ধাপে?
তাই আমি মায়ার্সের অ্যালগরিদম নিয়েছি, যেটি আপনি প্রতিবার git diff
চালানোর সময় ব্যবহার করেন এবং এটি আমার চ্যাপ্টা গাছে কাজ করতে দেন। একটি উদাহরণ দিয়ে ব্যাখ্যা করা যাক:
var oldList = ['ul', [ ['li', 'Item 1'], ['li', 'Item 2'], ]]; var newList = ['ul', [ ['li', 'Item 1'], ['li', 'Item 2'], ['li', 'Item 3'], ]];
আপনি দেখতে পাচ্ছেন, আমি DOM-এর সাথে কাজ করছি না, কিন্তু আইডিয়া 1-এ আমরা যে বস্তুর আক্ষরিক উপস্থাপনা দেখেছি তার সাথে। এখন, আপনি লক্ষ্য করবেন যে তালিকার শেষে আমাদের একটি নতুন <li>
যোগ করতে হবে।
চ্যাপ্টা গাছ দেখতে এইরকম:
var oldFlattened = ['O ul', 'O li', 'L Item 1', 'C li', 'O li', 'L Item 2', 'C li', 'C ul']; var newFlattened = ['O ul', 'O li', 'L Item 1', 'C li', 'O li', 'L Item 2', 'C li', 'O li', 'L Item 3', 'C li', 'C ul'];
O
এর অর্থ "ওপেন ট্যাগ", L
এর জন্য "আক্ষরিক" (এই ক্ষেত্রে, কিছু পাঠ্য) এবং C
এর অর্থ "ক্লোজ ট্যাগ"। মনে রাখবেন যে প্রতিটি গাছ এখন স্ট্রিংগুলির একটি তালিকা, এবং আর কোনো নেস্টেড অ্যারে নেই। চ্যাপ্টা বলতে আমি এটাই বুঝি।
যখন আমি এই উপাদানগুলির প্রতিটিতে একটি পার্থক্য চালাই (অ্যারের প্রতিটি আইটেমকে একটি ইউনিটের মতো আচরণ করা), আমি পাই:
var diff = [ ['keep', 'O ul'] ['keep', 'O li'] ['keep', 'L Item 1'] ['keep', 'C li'] ['keep', 'O li'] ['keep', 'L Item 2'] ['keep', 'C li'] ['add', 'O li'] ['add', 'L Item 3'] ['add', 'C li'] ['keep', 'C ul'] ];
আপনি সম্ভবত অনুমান করেছেন, আমরা তালিকার বেশিরভাগই রাখছি, এবং এটির শেষে একটি <li>
যোগ করছি। তারা আপনি দেখতে add
এন্ট্রি হয়.
যদি আমরা এখন তৃতীয় <li>
এর পাঠ্যটি Item 3
থেকে Item 4
তে পরিবর্তন করি এবং এটিতে একটি পার্থক্য চালাই, আমরা পেতে পারি:
var diff = [ ['keep', 'O ul'] ['keep', 'O li'] ['keep', 'L Item 1'] ['keep', 'C li'] ['keep', 'O li'] ['keep', 'L Item 2'] ['keep', 'C li'] ['keep', 'O li'] ['rem', 'L Item 3'] ['add', 'L Item 4'] ['keep', 'C li'] ['keep', 'C ul'] ];
আমি জানি না এই পদ্ধতিটি কতটা গাণিতিকভাবে অদক্ষ, তবে বাস্তবে এটি বেশ ভাল কাজ করেছে। এটি শুধুমাত্র খারাপভাবে সঞ্চালিত হয় যখন তাদের মধ্যে অনেক পার্থক্য আছে যে বড় গাছ diffing; যখন এটি মাঝে মাঝে ঘটে, আমি 200ms টাইমআউট অবলম্বন করি ডিফিংকে বাধা দিতে এবং কেবলমাত্র DOM-এর আপত্তিকর অংশ সম্পূর্ণরূপে প্রতিস্থাপন করি। যদি আমি একটি টাইমআউট ব্যবহার না করি, তবে ডিফ সম্পূর্ণ না হওয়া পর্যন্ত পুরো অ্যাপ্লিকেশনটি কিছু সময়ের জন্য স্টল থাকবে।
Myers ডিফ ব্যবহার করার একটি সৌভাগ্যের সুবিধা হল যে এটি সন্নিবেশের চেয়ে মুছে ফেলাকে অগ্রাধিকার দেয়: এর মানে হল যে যদি একটি আইটেম অপসারণ করা এবং একটি আইটেম যোগ করার মধ্যে সমানভাবে দক্ষ পছন্দ থাকে, তাহলে অ্যালগরিদম একটি আইটেমকে প্রথমে সরিয়ে দেবে। ব্যবহারিকভাবে, এটি আমাকে সমস্ত নির্মূল করা DOM উপাদানগুলি দখল করতে দেয় এবং পরে ডিফের প্রয়োজন হলে সেগুলিকে পুনর্ব্যবহার করতে সক্ষম হব। শেষ উদাহরণে, শেষ <li>
এর বিষয়বস্তু Item 3
থেকে Item 4
এ পরিবর্তন করে পুনর্ব্যবহার করা হয়। উপাদানগুলিকে পুনর্ব্যবহার করে (নতুন DOM উপাদানগুলি তৈরি করার পরিবর্তে) আমরা কার্যক্ষমতা এমন একটি ডিগ্রিতে উন্নত করি যেখানে ব্যবহারকারী বুঝতে পারে না যে DOM ক্রমাগত পুনরায় আঁকা হচ্ছে।
আপনি যদি ভাবছেন যে এই ফ্ল্যাটেনিং এবং ডিফিং মেকানিজমটি বাস্তবায়ন করা কতটা জটিল যা DOM-এ পরিবর্তনগুলি প্রযোজ্য, আমি এটি ES5 জাভাস্ক্রিপ্টের 500 লাইনে করতে পেরেছি, এবং এটি এমনকি ইন্টারনেট এক্সপ্লোরার 6 এও চলে৷ কিন্তু, স্বীকার করেই, এটি ছিল সম্ভবত আমার লেখা সবচেয়ে কঠিন কোড। একগুঁয়ে হওয়া একটি মূল্য আসে।
আমি যে চারটি ধারণা উপস্থাপন করতে চেয়েছিলাম! এগুলি সম্পূর্ণ মৌলিক নয় তবে আমি আশা করি এগুলি উপন্যাস এবং কারও কারও কাছে আকর্ষণীয় হবে। পড়ার জন্য ধন্যবাদ!