插槽
插槽允许组件将组件的 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
投影与 所有框架都需要一种方法让组件有条件地包装其复杂内容。这个问题可以用很多不同的方法解决,但主要有两种方法
- 投影:投影是一种声明式的方式,描述了内容如何从父模板传递到需要投影的位置。
children
:children
指的是将内容视为另一种输入的 vDOM 方法。
这两种方法可以最好地描述为声明式与命令式。它们都具有各自的优点和缺点。
Qwik 使用声明式投影方法,因为它需要能够独立于彼此渲染父组件和子组件。在命令式方法中,子组件可以修改 children
的方法不计其数。如果子组件依赖于其 children
,则每次父组件重新渲染时,它都将被迫重新渲染以重新应用更改。这种额外的渲染与 Qwik 的目标相矛盾,即让组件独立渲染。
注意:确保在
component$()
函数中使用<Slot />
以确保其正常工作。<Slot />
无法在 内联组件 中运行,内联组件类似于普通函数export const MyInlineComp = () =>
。
高级:插槽和上下文
插槽组件可以访问其父组件的 上下文,即使它们没有被投影。此外,如果父组件将 <Slot />
投影到另一个组件中,插槽组件也将可以访问该更深层组件的上下文。
但是,如果组件尚未被投影,因为 <Slot />
是有条件地渲染的,那么就无法知道更深层的上下文,然后插槽组件将只能看到直接父组件的上下文。
因此,最好避免这些情况;如果您正在提供上下文,请不要有条件地渲染您的 <Slot />
。