Slots
এই পৃষ্ঠাটি ধরে নেওয়া হচ্ছে আপনি ইতিমধ্যেই Components Basics পড়েছেন। আপনি যদি কম্পোনেন্টগুলিতে নতুন হন তবে প্রথমে এটি পড়ুন।
Slot Content and Outlet
আমরা শিখেছি যে কম্পোনেন্টগুলি প্রপস গ্রহণ করতে পারে, যা যেকোনো ধরনের জাভাস্ক্রিপ্ট মান হতে পারে। কিন্তু কিভাবে টেমপ্লেট বিষয়অবজেক্ট সম্পর্কে? কিছু ক্ষেত্রে, আমরা চাইল্ড কম্পোনেন্টে একটি টেমপ্লেট ফ্র্যাগমেন্ট পাঠাতে চাই এবং চাইল্ড কম্পোনেন্টকে তার নিজস্ব টেমপ্লেটের মধ্যে টুকরো রেন্ডার করতে দিতে পারি।
উদাহরণস্বরূপ, আমাদের একটি <FancyButton>
কম্পোনেন্ট থাকতে পারে যা এই ধরনের ব্যবহার সমর্থন করে:
template
<FancyButton>
Click me! <!-- slot content -->
</FancyButton>
<FancyButton>
এর টেমপ্লেটটি দেখতে এইরকম:
template
<button class="fancy-btn">
<slot></slot> <!-- slot outlet -->
</button>
<slot>
কম্পোনেন্ট হল একটি স্লট আউটলেট যা নির্দেশ করে যে অভিভাবক-প্রদত্ত স্লট সামগ্রী কোথায় রেন্ডার করা উচিত।
এবং চূড়ান্ত রেন্ডার করা DOM:
html
<button class="fancy-btn">Click me!</button>
স্লটের সাথে, <FancyButton>
বাইরের <button>
(এবং এর অভিনব স্টাইলিং) রেন্ডার করার জন্য দায়ী, যখন ভিতরের বিষয়অবজেক্ট মূল কম্পোনেন্ট দ্বারা সরবরাহ করা হয়।
স্লট বোঝার আরেকটি উপায় হল জাভাস্ক্রিপ্ট ফাংশনের সাথে তুলনা করা:
js
// parent component passing slot content
FancyButton('Click me!')
// FancyButton renders slot content in its own template
function FancyButton(slotContent) {
return `<button class="fancy-btn">
${slotContent}
</button>`
}
স্লট বিষয়অবজেক্ট শুধু পাঠ্যের মধ্যে সীমাবদ্ধ নয়। এটা কোনো বৈধ টেমপ্লেট বিষয়অবজেক্ট হতে পারে. উদাহরণস্বরূপ, আমরা একাধিক কম্পোনেন্ট, বা এমনকি অন্যান্য কম্পোনেন্ট পাস করতে পারি:
template
<FancyButton>
<span style="color:red">Click me!</span>
<AwesomeIcon name="plus" />
</FancyButton>
স্লট ব্যবহার করে, আমাদের <FancyButton>
আরও নমনীয় এবং পুনরায় ব্যবহারযোগ্য। আমরা এখন এটিকে বিভিন্ন জায়গায় বিভিন্ন অভ্যন্তরীণ বিষয়অবজেক্ট সহ ব্যবহার করতে পারি, তবে একই অভিনব স্টাইলিং সহ।
Vue কম্পোনেন্টের স্লট মেকানিজম native Web Component <slot>
element দ্বারা অনুপ্রাণিত, কিন্তু অতিরিক্ত ক্ষমতা সহ যা আমরা পরে দেখব।
Render Scope
স্লট বিষয়অবজেক্টর প্যারেন্ট কম্পোনেন্টের ডেটা স্কোপের অ্যাক্সেস আছে, কারণ এটি প্যারেন্টে সংজ্ঞায়িত করা হয়েছে। উদাহরণ স্বরূপ:
template
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>
এখানে উভয় {{ message }}
ইন্টারপোলেশন একই বিষয়অবজেক্ট রেন্ডার করবে।
স্লট বিষয়অবজেক্টর চাইল্ড কম্পোনেন্টের ডেটাতে অ্যাক্সেস না থাকে। Vue টেমপ্লেটের অভিব্যক্তিগুলি শুধুমাত্র জাভাস্ক্রিপ্টের আভিধানিক স্কোপিংয়ের সাথে সামঞ্জস্যপূর্ণ, এটি সংজ্ঞায়িত স্কোপ অ্যাক্সেস করতে পারে। অন্য কথায়:
অভিভাবক টেমপ্লেটের অভিব্যক্তিগুলির শুধুমাত্র অভিভাবক সুযোগে অ্যাক্সেস আছে; চাইল্ড টেমপ্লেটের এক্সপ্রেশনের শুধুমাত্র চাইল্ড স্কোপের অ্যাক্সেস আছে।
Fallback Content
এমন কিছু ক্ষেত্রে আছে যখন একটি স্লটের জন্য ফলব্যাক (অর্থাৎ ডিফল্ট) বিষয়অবজেক্ট নির্দিষ্ট করা উপযোগী, শুধুমাত্র তখনই রেন্ডার করা হবে যখন কোনো বিষয়অবজেক্ট প্রদান করা হয় না। উদাহরণস্বরূপ, একটি <SubmitButton>
কম্পোনেন্টে:
template
<button type="submit">
<slot></slot>
</button>
অভিভাবক যদি কোনো স্লট সামগ্রী প্রদান না করেন তাহলে আমরা হয়ত "জমা দিন" পাঠ্যটিকে <button>
-এর মধ্যে রেন্ডার করতে চাই। ফলব্যাক বিষয়অবজেক্ট "জমা দিন" করতে, আমরা এটিকে <slot>
ট্যাগের মধ্যে রাখতে পারি:
template
<button type="submit">
<slot>
Submit <!-- fallback content -->
</slot>
</button>
এখন যখন আমরা একটি প্যারেন্ট কম্পোনেন্টে <SubmitButton>
ব্যবহার করি, স্লটের জন্য কোনো বিষয়অবজেক্ট প্রদান করি না:
template
<SubmitButton />
এটি ফলব্যাক সামগ্রী রেন্ডার করবে, "Submit":
html
<button type="submit">Submit</button>
কিন্তু যদি আমরা সামগ্রী প্রদান করি:
template
<SubmitButton>Save</SubmitButton>
তারপর প্রদত্ত বিষয়অবজেক্ট পরিবর্তে রেন্ডার করা হবে:
html
<button type="submit">Save</button>
Named Slots
এমন সময় আছে যখন একটি একক কম্পোনেন্টে একাধিক স্লট আউটলেট থাকা দরকারী। উদাহরণস্বরূপ, নিম্নলিখিত টেমপ্লেট সহ একটি <BaseLayout>
কম্পোনেন্টে:
template
<div class="container">
<header>
<!-- We want header content here -->
</header>
<main>
<!-- We want main content here -->
</main>
<footer>
<!-- We want footer content here -->
</footer>
</div>
এই ক্ষেত্রে, <slot>
কম্পোনেন্টটির একটি বিশেষ বৈশিষ্ট্য রয়েছে, name
, যা বিভিন্ন স্লটে একটি অনন্য আইডি বরাদ্দ করতে ব্যবহার করা যেতে পারে যাতে আপনি নির্ধারণ করতে পারেন যে সামগ্রী কোথায় রেন্ডার করা হবে:
template
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
নাম
ছাড়া একটি <slot>
আউটলেটের অন্তর্নিহিত নাম "ডিফল্ট" আছে।
<BaseLayout>
ব্যবহার করে একটি প্যারেন্ট কম্পোনেন্টে, আমাদের একাধিক স্লট কন্টেন্ট ফ্র্যাগমেন্ট পাস করার একটি উপায় প্রয়োজন, প্রতিটি আলাদা স্লট আউটলেটকে লক্ষ্য করে। এখানেই নামকৃত স্লট আসে।
একটি নামযুক্ত স্লট পাস করার জন্য, আমাদের v-slot
নির্দেশের সাথে একটি <template>
কম্পোনেন্ট ব্যবহার করতে হবে এবং তারপরে v-slot
-এ আর্গুমেন্ট হিসেবে স্লটের নাম পাস করতে হবে:
template
<BaseLayout>
<template v-slot:header>
<!-- content for the header slot -->
</template>
</BaseLayout>
v-slot
এর একটি ডেডিকেটেড শর্টহ্যান্ড #
আছে, তাই <template v-slot:header>
কে শুধু <template #header>
-এ ছোট করা যেতে পারে। এটিকে " চাইল্ড কম্পোনেন্টের 'শিরোনাম' স্লটে এই টেমপ্লেট খণ্ডটি রেন্ডার করুন" হিসাবে ভাবুন৷
শর্টহ্যান্ড সিনট্যাক্স ব্যবহার করে <BaseLayout>
-এ তিনটি স্লটের জন্য কোড পাস করার বিষয়অবজেক্ট এখানে রয়েছে:
template
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
যখন একটি কম্পোনেন্ট একটি ডিফল্ট স্লট এবং নামযুক্ত স্লট উভয়ই গ্রহণ করে, তখন সমস্ত শীর্ষ-স্তরের অ-<টেমপ্লেট>
নোডগুলিকে ডিফল্ট স্লটের জন্য বিষয়অবজেক্ট হিসাবে অন্তর্নিহিতভাবে বিবেচনা করা হয়। সুতরাং উপরেরটি এভাবেও লেখা যেতে পারে:
template
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<!-- implicit default slot -->
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
এখন <template>
কম্পোনেন্টের ভিতরের সবকিছু সংশ্লিষ্ট স্লটে পাঠানো হবে। চূড়ান্ত রেন্ডার করা HTML হবে:
html
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
আবার, এটি আপনাকে জাভাস্ক্রিপ্ট ফাংশন সাদৃশ্য ব্যবহার করে নামযুক্ত স্লটগুলি আরও ভালভাবে বুঝতে সাহায্য করতে পারে:
js
// passing multiple slot fragments with different names
BaseLayout({
header: `...`,
default: `...`,
footer: `...`
})
// <BaseLayout> renders them in different places
function BaseLayout(slots) {
return `<div class="container">
<header>${slots.header}</header>
<main>${slots.default}</main>
<footer>${slots.footer}</footer>
</div>`
}
Conditional Slots
কখনও কখনও আপনি একটি স্লট উপস্থিত আছে কিনা তার উপর ভিত্তি করে কিছু রেন্ডার করতে চান।
আপনি এটি অর্জন করতে v-if এর সাথে একত্রে $slots বৈশিষ্ট্য ব্যবহার করতে পারেন।
নীচের উদাহরণে আমরা তিনটি শর্তসাপেক্ষ স্লট সহ একটি কার্ড কম্পোনেন্ট সংজ্ঞায়িত করি: header
, footer
এবং default
একটি। শিরোনাম/পাদলেখ/ডিফল্ট উপস্থিত থাকলে আমরা অতিরিক্ত স্টাইলিং প্রদানের জন্য সেগুলিকে মোড়ানো করতে চাই:
template
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div v-if="$slots.default" class="card-content">
<slot />
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template>
Dynamic Slot Names
Dynamic directive arguments এছাড়াও v-slot
-এ কাজ করে, যা গতিশীল স্লট নামের সংজ্ঞাকে অনুমতি দেয়:
template
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- with shorthand -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
মনে রাখবেন এক্সপ্রেশনটি ডায়নামিক নির্দেশিক আর্গুমেন্টের সিনট্যাক্স সীমাবদ্ধতা এর অধীন।
Scoped Slots
Render Scope এ যেমন আলোচনা করা হয়েছে, স্লট কন্টেন্টের চাইল্ড কম্পোনেন্টে স্টেটের অ্যাক্সেস নেই।
যাইহোক, এমন কিছু ক্ষেত্রে এটি কার্যকর হতে পারে যদি একটি স্লটের বিষয়অবজেক্ট প্যারেন্ট স্কোপ এবং চাইল্ড স্কোপ উভয়ের ডেটা ব্যবহার করতে পারে। এটি অর্জন করার জন্য, রেন্ডার করার সময় চাইল্ডর একটি স্লটে ডেটা পাস করার জন্য আমাদের একটি উপায় প্রয়োজন।
প্রকৃতপক্ষে, আমরা ঠিক এটি করতে পারি - আমরা একটি স্লট আউটলেটে বৈশিষ্ট্যগুলি পাস করতে পারি ঠিক যেমন একটি কম্পোনেন্টে প্রপস পাস করা:
template
<!-- <MyComponent> template -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
একটি একক ডিফল্ট স্লট বনাম নামযুক্ত স্লট ব্যবহার করার সময় স্লট প্রপগুলি গ্রহণ করা কিছুটা আলাদা। চাইল্ড কম্পোনেন্ট ট্যাগে সরাসরি v-slot
ব্যবহার করে প্রথমে একটি একক ডিফল্ট স্লট ব্যবহার করে প্রপস কীভাবে গ্রহণ করা যায় তা আমরা দেখাতে যাচ্ছি:
template
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
সন্তানের দ্বারা স্লটে পাস করা প্রপগুলি সংশ্লিষ্ট v-slot
নির্দেশের মান হিসাবে উপলব্ধ, যা স্লটের ভিতরে অভিব্যক্তি দ্বারা অ্যাক্সেস করা যেতে পারে।
আপনি একটি স্কোপড স্লটকে চাইল্ড কম্পোনেন্টে একটি ফাংশন পাস করার মতো ভাবতে পারেন। চাইল্ড কম্পোনেন্টটি তখন এটিকে কল করে, আর্গুমেন্ট হিসাবে প্রপস পাস করে:
js
MyComponent({
// passing the default slot, but as a function
default: (slotProps) => {
return `${slotProps.text} ${slotProps.count}`
}
})
function MyComponent(slots) {
const greetingMessage = 'hello'
return `<div>${
// call the slot function with props!
slots.default({ text: greetingMessage, count: 1 })
}</div>`
}
প্রকৃতপক্ষে, এটি কীভাবে স্কোপড স্লটগুলি সংকলিত হয় এবং কীভাবে আপনি ম্যানুয়ালটিতে স্কোপড স্লটগুলি ব্যবহার করবেন তার খুব কাছাকাছি।render functions.
লক্ষ্য করুন কিভাবে v-slot="slotProps"
স্লট ফাংশনের স্বাক্ষরের সাথে মেলে। ঠিক ফাংশন আর্গুমেন্টের মতই, আমরা v-slot
-এ destructuring ব্যবহার করতে পারি:
template
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
Named Scoped Slots
নামযুক্ত স্কোপড স্লট একইভাবে কাজ করে - স্লট প্রপগুলি v-slot
নির্দেশের মান হিসাবে অ্যাক্সেসযোগ্য: v-slot:name="slotProps"
৷ শর্টহ্যান্ড ব্যবহার করার সময়, এটি এই মত দেখায়:
template
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
একটি নামযুক্ত স্লটে প্রপস পাস করা:
template
<slot name="header" message="hello"></slot>
নোট করুন একটি স্লটের name
প্রপস-এ অন্তর্ভুক্ত করা হবে না কারণ এটি সংরক্ষিত - তাই ফলস্বরূপ headerProps
হবে { message: 'hello' }
।
আপনি যদি ডিফল্ট স্কোপড স্লটের সাথে নামযুক্ত স্লটগুলি মিশ্রিত করেন তবে আপনাকে ডিফল্ট স্লটের জন্য একটি স্পষ্ট <template>
ট্যাগ ব্যবহার করতে হবে। কম্পোনেন্টে সরাসরি v-slot
নির্দেশনা রাখার চেষ্টা করলে একটি সংকলন ত্রুটি দেখা দিবে। এটি ডিফল্ট স্লটের প্রপসের সুযোগ সম্পর্কে কোনও অস্পষ্টতা এড়াতে। উদাহরণ স্বরূপ:
template
<!-- <MyComponent> template -->
<div>
<slot :message="hello"></slot>
<slot name="footer" />
</div>
template
<!-- This template won't compile -->
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<!-- message belongs to the default slot, and is not available here -->
<p>{{ message }}</p>
</template>
</MyComponent>
ডিফল্ট স্লটের জন্য একটি স্পষ্ট <template>
ট্যাগ ব্যবহার করা এটি স্পষ্ট করতে সাহায্য করে যে অন্য স্লটের ভিতরে message
প্রপ উপলব্ধ নেই:
template
<MyComponent>
<!-- Use explicit default slot -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
Fancy List Example
আপনি হয়তো ভাবছেন যে স্কোপড স্লটের জন্য একটি ভাল ব্যবহারের ক্ষেত্রে কী হবে। এখানে একটি উদাহরণ দেওয়া হল: একটি <FancyList>
কম্পোনেন্টের কল্পনা করুন যা আইটেমগুলির একটি তালিকা রেন্ডার করে - এটি দূরবর্তী ডেটা লোড করার জন্য, একটি তালিকা প্রদর্শন করতে ডেটা ব্যবহার করে, এমনকি পৃষ্ঠা সংখ্যা বা অসীম স্ক্রলিংয়ের মতো উন্নত বৈশিষ্ট্যগুলিকে এনক্যাপসুলেট করতে পারে৷ যাইহোক, আমরা চাই যে প্রতিটি আইটেম দেখতে কেমন হবে তার সাথে এটি নমনীয় হোক এবং প্রতিটি আইটেমের স্টাইলিংটি মূল কম্পোনেন্টের উপর ছেড়ে দিন যা এটি ব্যবহার করে। তাই পছন্দসই ব্যবহার এই মত দেখতে পারে:
template
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p>by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>
<FancyList>
-এর ভিতরে, আমরা একই <slot>
বিভিন্ন আইটেম ডেটার সাথে একাধিকবার রেন্ডার করতে পারি (লক্ষ্য করুন আমরা একটি অবজেক্টকে স্লট প্রপস হিসেবে পাস করতে v-bind
ব্যবহার করছি):
template
<ul>
<li v-for="item in items">
<slot name="item" v-bind="item"></slot>
</li>
</ul>
Renderless Components
আমরা উপরে আলোচনা করা <FancyList>
ব্যবহারের ক্ষেত্রে স্কোপড স্লটের মাধ্যমে ভিজ্যুয়াল আউটপুটের অংশ ভোক্তাদের কাছে অর্পণ করার সময় পুনঃব্যবহারযোগ্য লজিক (ডেটা আনা, পেজিনেশন ইত্যাদি) এবং ভিজ্যুয়াল আউটপুট উভয়কেই অন্তর্ভুক্ত করে।
আমরা যদি এই ধারণাটিকে আরও কিছুটা এগিয়ে দেই, তাহলে আমরা এমন কম্পোনেন্ট নিয়ে আসতে পারি যা শুধুমাত্র যুক্তিকে এনক্যাপসুলেট করে এবং নিজে থেকে কিছু রেন্ডার করে না - ভিজ্যুয়াল আউটপুট সম্পূর্ণরূপে স্কোপড স্লট সহ ভোক্তা কম্পোনেন্টের কাছে অর্পণ করা হয়। আমরা এই ধরনের কম্পোনেন্টকে রেন্ডারলেস কম্পোনেন্ট বলি।
একটি উদাহরণ রেন্ডারলেস কম্পোনেন্ট হতে পারে যা বর্তমান মাউসের অবস্থান ট্র্যাক করার যুক্তিকে এনক্যাপসুলেট করে:
template
<MouseTracker v-slot="{ x, y }">
Mouse is at: {{ x }}, {{ y }}
</MouseTracker>
While an interesting pattern, most of what can be achieved with Renderless Components can be achieved in a more efficient fashion with Composition API, without incurring the overhead of extra component nesting. Later, we will see how we can implement the same mouse tracking functionality as a Composable.
এটি বলেছে, স্কোপড স্লটগুলি এখনও সেই ক্ষেত্রে কার্যকর যেখানে আমাদের যুক্তি এবং উভয়ই ভিজ্যুয়াল আউটপুট রচনা করতে হবে, যেমন <FancyList>
উদাহরণে।