Saltar al contenido
Meeko
Escribe para buscar en las guías y en la referencia de la API. Ábrela desde cualquier página con / o Ctrl K; muévete con las flechas y entra con Intro.
Empezar

Contenidos

En esta página

defineSnippets

Declara los snippets de un componente, contenido de plantilla que el padre escribe entre sus etiquetas, con sus parámetros tipados.

macro Desde 1.1.0 Código fuente

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#

meeko
<!-- 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>
meeko
<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#

ts
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#

meeko
<!-- 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>
meeko
<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:

meeko
<!-- 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:

meeko
<!-- 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>
meeko
<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:

meeko
<!-- 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#

Incorrectomeeko
<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: [] }>().

Correctomeeko
<script lang="ts">
const snippets = defineSnippets<{ default: [] }>();
</script>

Pintar un snippet sin sus argumentos#

Incorrectomeeko
<render snippet="snippets.actions" />

Con actions: [close: () => void], meeko check da TS2345: Argument of type '[]' is not assignable to parameter of type '[close: () => void]'.

Correctomeeko
<render snippet="snippets.actions" args="close" />

No pasar un snippet obligatorio, o pasar uno que no existe#

Incorrectomeeko
<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]; }>'.

Correctomeeko
<AppDialog>
  <p>Texto</p>
  <snippet name="title">Pie</snippet>
</AppDialog>

Contenido para un componente sin snippets#

Un componente sin defineSnippets no admite contenido.

Incorrectomeeko
<!-- 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:

Correctomeeko
<!-- 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 un computed, porque el padre puede empezar o dejar de pasarlo con <snippet if:when>.
  • Dónde va. Una sola vez, en su propia declaración const de nivel superior con un nombre simple (MEEK059), antes del primer await de navegador (MEEK058).
  • Tipos. Un valor que no es una tupla ni un Snippet es TS2344. 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 como snippets['cell-email'].

Relacionado#

¿Te sirvió esta página?

Añadir un comentario

Se envía al elegir «Sí» o «No». Solo se guardan la página, tu respuesta y el comentario: ni cookies ni datos tuyos.

0 de 2000 caracteres

Editar en GitHub: docs/reference/macros/definesnippets.md