defineSnippets
Declara los snippets de un componente, contenido de plantilla que el padre escribe entre sus etiquetas, con sus parámetros tipados.
defineSnippets<T>() declara los snippets del componente: trozos de plantilla que el padre escribe entre las etiquetas del hijo y que el hijo pinta donde quiere con <render>. Cada clave de T es un snippet y su valor, la tupla de parámetros que el hijo le pasa; ? lo hace opcional. Es una macro de compilación: no se importa.
El contenido suelto entre las etiquetas es el snippet default; los demás se escriben con <snippet name="…">.
Uso#
<!-- app/components/AppDialog.meeko -->
<script lang="ts">
const snippets = defineSnippets<{ default: []; title?: []; actions?: [close: () => void] }>();
const open = signal(true);
function close(): void {
open.value = false;
}
</script>
<template>
<section if:when="open.value">
<h2 if:when="snippets.title"><render snippet="snippets.title" /></h2>
<render snippet="snippets.default" />
<footer if:when="snippets.actions"><render snippet="snippets.actions" args="close" /></footer>
</section>
</template><AppDialog>
<p>¿Borrar la nota?</p>
<snippet name="title">Confirmar</snippet>
<snippet name="actions" params="close"><button type="button" on:click="close">Cancelar</button></snippet>
</AppDialog>Tipo#
const snippets = defineSnippets<{
default: [];
title?: [];
actions?: [close: () => void];
}>()Parámetros#
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
T (argumento de tipo) |
{ [nombre]: [...params] | Snippet<Args> } |
obligatorio | Cada clave es un snippet y su valor, la tupla de parámetros ([] sin parámetros). Desde 1.4, el valor también puede ser un Snippet<Args>, que cuenta como Args. Desde 1.8, las claves pueden salir de un tipo mapeado sobre un genérico (`cell-${K}`). Una clave opcional (title?:) hace opcional el snippet. Sin argumento de tipo es MEEK060. |
No admite argumentos en ejecución (MEEK060).
Valor devuelto#
Un objeto con un Snippet<Args> por clave (undefined si el padre no pasa uno opcional). Se usa en la plantilla:
<render snippet="snippets.x" args="a, b" />pinta el snippet con esos argumentos;if:when="snippets.x"comprueba si el padre lo pasó.
Los snippets no son props: no aparecen en ComponentProps. El módulo compilado exporta su tipo en ComponentSnippets, y cada componente autoimportado tiene además <Nombre>Snippets (AppDialogSnippets).
Ejemplos#
Una lista con fila y estado vacío#
<!-- app/components/AppList.meeko -->
<script lang="ts" generic="T extends { id: string }">
const props = defineProps<{ items: T[] }>();
const snippets = defineSnippets<{ item: [item: T, index: number]; empty?: [] }>();
</script>
<template>
<ul if:when="props.items.length">
<li each:of="props.items" each:as="item" each:key="item.id" each:index="i"><render snippet="snippets.item" args="item, i" /></li>
</ul>
<div if:when="!props.items.length"><render snippet="snippets.empty" /></div>
</template><AppList bind:items="notes.value">
<snippet name="item" params="note, index">{index + 1}. {note.title}</snippet>
<snippet name="empty">No hay notas.</snippet>
</AppList>Reutilizar los snippets de otro componente#
Desde 1.4, T puede partir del tipo de snippets de otro componente y recortarlo o ampliarlo. Un modal que envuelve una card declara los de la card salvo footer, añade actions y los reenvía:
<!-- app/components/AppModal.meeko -->
<script lang="ts">
const open = defineModel<boolean>();
const snippets = defineSnippets<Omit<AppCardSnippets, "footer"> & { actions?: [close: () => void] }>();
function close(): void {
open.value = false;
}
</script>
<template>
<AppCard if:when="open.value">
<render snippet="snippets.default" />
<snippet name="footer" if:when="snippets.actions"><render snippet="snippets.actions" args="close" /></snippet>
</AppCard>
</template>AppCardSnippets se autoimporta con el nombre de la etiqueta. Cada snippet conserva sus parámetros y su ?.
Snippets por columna, con nombres que salen de un genérico#
Desde 1.8, las claves de T pueden salir de un parámetro de tipo con un tipo mapeado y un template literal. Una rejilla genérica declara cell-<clave>, con el valor del tipo de esa columna, header-<clave> y un cell de respaldo:
<!-- app/components/AppGrid.meeko -->
<script lang="ts" generic="Row extends object">
import type { Snippet } from "@meekojs/meeko";
type Column<R extends object> = { key: Extract<keyof R, string>; label: string };
const props = defineProps<{ rows: readonly Row[]; columns: readonly Column<Row>[] }>();
const snippets = defineSnippets<
{ [K in Extract<keyof Row, string> as `cell-${K}`]?: [row: Row, value: Row[K]] } &
{ [K in Extract<keyof Row, string> as `header-${K}`]?: [column: Column<Row>] } &
{ cell?: [row: Row, column: Column<Row>, value: unknown] }
>();
// Sobre Row, TypeScript no lee un nombre calculado: el hijo lo lee de una vista.
const byName = snippets as { readonly [name: string]: Snippet<never> | undefined };
function cellOf(column: Column<Row>): Snippet<[row: Row, value: unknown]> | undefined {
return byName[`cell-${column.key}`] as Snippet<[row: Row, value: unknown]> | undefined;
}
</script>
<template>
<p each:of="props.rows" each:as="row">
<span each:of="props.columns" each:as="column">
<render if:when="cellOf(column)" snippet="cellOf(column)" args="row, row[column.key]" />
<render if:when="!cellOf(column)" snippet="snippets.cell" args="row, column, row[column.key]" />
</span>
</p>
</template><AppGrid bind:rows="users.value" bind:columns="columns">
<snippet name="cell-email" params="row, value"><a bind:href="'mailto:' + value">{value}</a></snippet>
<snippet name="header-email">Correo</snippet>
</AppGrid>value es string, el tipo de User["email"]; name="cell-nope" da TS2353 y unos params de más, TS2345. El editor completa los nombres posibles y muestra su firma. Dentro del hijo, snippets.cell conserva su tupla (el compilador lee la intersección miembro a miembro), pero un nombre calculado necesita la vista con conversión.
Un snippet con varias acciones#
Los parámetros pueden ser funciones del hijo, que el padre usa sin conocer su estado:
<!-- app/components/AppStepper.meeko -->
<script lang="ts">
const props = defineProps<{ steps: number }>();
const snippets = defineSnippets<{ step: [index: number, next: () => void, back: () => void] }>();
const current = signal(0);
const next = (): void => { current.value = Math.min(current.value + 1, props.steps - 1); };
const back = (): void => { current.value = Math.max(current.value - 1, 0); };
</script>
<template><render snippet="snippets.step" args="current.value, next, back" /></template>Errores comunes#
Argumentos en ejecución, o sin argumento de tipo#
<script lang="ts">
const snippets = defineSnippets<{ default: [] }>({ default: [] });
</script>MEEK060: defineSnippets no admite argumentos en tiempo de ejecución; declara los snippets en su argumento de tipo. Sin argumento de tipo: MEEK060: defineSnippets necesita un argumento de tipo, como defineSnippets<{ default: [] }>().
<script lang="ts">
const snippets = defineSnippets<{ default: [] }>();
</script>Pintar un snippet sin sus argumentos#
<render snippet="snippets.actions" />Con actions: [close: () => void], meeko check da TS2345: Argument of type '[]' is not assignable to parameter of type '[close: () => void]'.
<render snippet="snippets.actions" args="close" />No pasar un snippet obligatorio, o pasar uno que no existe#
<AppDialog>
<snippet name="footer">Pie</snippet>
</AppDialog>meeko check da TS2741: Property 'default' is missing in type … y TS2353: Object literal may only specify known properties, and '"footer"' does not exist in type 'SnippetMap<{ default: []; title?: []; actions?: [close: () => void]; }>'.
<AppDialog>
<p>Texto</p>
<snippet name="title">Pie</snippet>
</AppDialog>Contenido para un componente sin snippets#
Un componente sin defineSnippets no admite contenido.
<!-- app/components/AppBox.meeko, sin defineSnippets -->
<AppBox><p>Hola</p></AppBox>meeko check da TS2322: Type 'Snippet<any[]>' is not assignable to type '"this component declares no defineSnippets, so it accepts no children or <snippet>"'. Declara default en el hijo:
<!-- app/components/AppBox.meeko -->
<script lang="ts">
const snippets = defineSnippets<{ default: [] }>();
</script>
<template><div class="box"><render snippet="snippets.default" /></div></template>Notas#
- Ámbito. El contenido de un snippet pertenece al padre: lee sus señales y funciones, sigue siendo reactivo y se libera con el padre o el hijo. Lo que el hijo quiera darle va en los argumentos.
- SSR e hidratación. El snippet se pinta dentro del DOM del hijo, entre comentarios de marca, igual en el servidor que en el navegador.
- Copias en el setup. Como con las props,
const hasFooter = Boolean(snippets.footer)conserva el valor inicial: léelo en la plantilla o en uncomputed, porque el padre puede empezar o dejar de pasarlo con<snippet if:when>. - Dónde va. Una sola vez, en su propia declaración
constde nivel superior con un nombre simple (MEEK059), antes del primerawaitde navegador (MEEK058). - Tipos. Un valor que no es una tupla ni un
SnippetesTS2344. Las tuplas con nombre ([close: () => void]) dan nombres a los parámetros en el editor. - Nombres. Una clave es un identificador o, desde
1.8, segmentos unidos por guiones (cell-email); el hijo la lee comosnippets['cell-email'].