插槽
插槽允許元件將其 JSX 子元件視為一種輸入形式,並將這些子元件投影到元件的 DOM 樹中。
這個概念在不同的框架中有不同的名稱
- 在 Angular 中稱為內容投影
- 在 React 中,這是 props 的
children
- 在 Web 元件中,它也是
<slot>
實現此目標的主要 API 是 <Slot>
元件,在 @builder.io/qwik
中匯出
import { Slot, component$ } from '@builder.io/qwik';
const Button = component$(() => {
return (
<button>
Content: <Slot />
</button>
);
});
export default component$(() => {
return (
<Button>
This goes inside {'<Button>'} component marked by{`<Slot>`}
</Button>
);
});
<Slot>
元件是元件子項的佔位符。 <Slot>
元件將在渲染期間被元件的子項替換。
注意:Qwik 中的插槽是宣告式的,允許 Qwik 單獨渲染父元件和子元件。因為插槽是宣告式的,所以子元件不能被元件讀取或轉換。
Qwik 中的插槽就像內容的指定佔位符,允許元件保持獨立並避免不必要的重新渲染。這種設定保持了靈活性和易於管理,與直接子項方法不同,在直接子項方法中,父元件的更改可能會導致頻繁且複雜的子元件更新。插槽有助於維護順暢且高效的元件結構。
在一些特殊情況下,需要根據子元件完成一些工作,並且了解父元件和子元件一起懶載入的缺點,那麼內嵌元件是另一種選擇。
命名插槽
只要 Slot
元件具有不同的 name
屬性,就可以在同一個元件中多次使用它
import { Slot, component$, useStylesScoped$ } from '@builder.io/qwik';
import CSS from './index.css?inline';
const Tab = component$(() => {
useStylesScoped$(CSS);
return (
<section>
<h2>
<Slot name="title" />
</h2>
<div>
<Slot /> {/* default slot */}
<div>
<Slot name="footer" />
</div>
</div>
</section>
);
});
export default component$(() => {
return (
<Tab>
<div q:slot="title">Qwik</div>
<div>A resumable framework for building instant web applications</div>
<span q:slot="footer">made with ❤️ by </span>
<a q:slot="footer" href="https://builder.io">
builder.io
</a>
</Tab>
);
});
現在,當使用 <Tab>
元件時,我們可以傳遞子元件並使用 q:slot
屬性指定它們應該放置在哪個插槽中
請注意
- 如果未指定
q:slot
或它是空字串,則內容將被投影到預設<Slot>
中,即沒有name
屬性的<Slot>
。 - 多個
q:slot="footer"
屬性在內容投影中合併在一起。
未投影的內容
Qwik 會保留所有內容,即使未投影也是如此。這是因為內容可能會在未來投影。當投影的內容與任何 <Slot>
元件不匹配時,內容將被移動到惰性 <q:template>
元素中。
import { Slot, component$, useSignal } from '@builder.io/qwik';
const Accordion = component$(() => {
const isOpen = useSignal(false);
return (
<div>
<h1 onClick$={() => (isOpen.value = !isOpen.value)}>
{isOpen.value ? '▼' : '▶︎'}
</h1>
{isOpen.value && <Slot />}
</div>
);
});
export default component$(() => {
return (
<Accordion>
I am pre-rendered on the Server and hidden until needed.
</Accordion>
);
});
結果是
<div>
<h1>▶︎</h1>
</div>
<q:template q:slot hidden aria-hidden="true">
I am pre-rendered on the Server and hidden until needed.
</q:template>
請注意,未投影的內容已移至惰性 <q:template>
中。這樣做是為了防止 Accordion
元件在 <Slot>
中重新渲染。在這種情況下,我們避免了僅僅為了生成投影內容而重新渲染父元件。通過在初始渲染父元件時保留未投影的內容,兩個元件的渲染可以保持獨立。
無效的投影
q:slot
屬性必須是元件的直接子項。
import { component$ } from '@builder.io/qwik';
export const Project = component$(() => { ... })
export const MyApp = component$(() => {
return (
<Project>
<span q:slot="title">ok, direct child of Project</span>
<div>
<span q:slot="title">Error, not a direct child of Project</span>
</div>
</Project>
);
});
進階範例
一個可折疊元件的範例,該元件有條件地投影可編輯的內容。
import { Slot, component$, useSignal } from '@builder.io/qwik';
export const Collapsible = component$(() => {
const isOpen = useSignal(true);
return (
<div>
<h1 onClick$={() => (isOpen.value = !isOpen.value)}>
{isOpen.value ? '▼' : '▶︎'}
<Slot name="title" />
</h1>
{isOpen.value && <Slot />}
</div>
);
});
export default component$(() => {
const title = useSignal('Qwik');
const description = useSignal(
'A resumable framework for building instant web applications'
);
return (
<>
<label>Title</label>
<input bind:value={title} type="text" />
<label>Description</label>
<textarea bind:value={description} cols={50} />
<hr />
<Collapsible>
<span q:slot="title">{title}</span>
{description}
</Collapsible>
</>
);
});
Collapsible
元件將始終顯示標題,但只有在 store.isOpen
為 true
時才會顯示文字主體。
此外,投影內容的 title
和 description
是可編輯的。
- 父元件需要能夠在不強制
Collapsible
元件重新渲染的情況下更改內容。 - 子元件需要在不重新渲染父元件的情況下更改投影的內容。在我們的例子中,
Collapsible
應該能夠顯示/隱藏預設的q:slot
,而無需下載和重新渲染父元件。
為了讓兩個元件具有獨立的生命週期,投影需要是宣告式的。這樣,父元件或子元件都可以更改投影的內容或投影的方式,而無需強制重新渲染另一個元件。
children
投影 vs 所有框架都需要一種方法讓組件能夠根據條件包裹其複雜的內容。這個問題可以用許多不同的方式解決,但主要有兩種方法
- 投影:投影是一種宣告式的方式,用於描述內容如何從父模板到達需要投影的位置。
children
:children
是指將內容視為另一個輸入的 vDOM 方法。
這兩種方法可以最好地描述為宣告式與指令式。它們都有各自的優缺點。
Qwik 使用宣告式投影方法,因為它需要能夠獨立於彼此渲染父組件和子組件。在指令式方法中,子組件可以通過無數種方式修改 children
。如果子組件依賴於其 children
,則每次父組件重新渲染以重新應用更改時,它都將被迫重新渲染。這種額外的渲染與 Qwik 的組件隔離渲染目標相矛盾。
注意:請確保在
component$()
函數中使用<Slot />
以確保其正常工作。<Slot />
無法在內嵌組件中運行,內嵌組件類似於普通函數export const MyInlineComp = () =>
。
進階:插槽和上下文
插槽組件可以訪問其父組件的上下文,即使它們沒有被投影。此外,如果父組件正在另一個組件內部投影 <Slot />
,則插槽組件也將可以訪問該更深層組件的上下文。
但是,如果組件尚未被投影,因為 <Slot />
是有條件地渲染的,則無法知道更深層的上下文,然後插槽組件將只看到直接父組件的上下文。
因此,最安全的做法是避免這些情況;如果您要提供上下文,請不要有條件地渲染您的 <Slot />
。