Skip to content
← Todas las preguntas
⚡ Intermedio

¿Cómo funciona el atributo `is` para el renderizado dinámico de componentes?

🧩 Componentes⚡ Vite

El atributo is en <component> acepta un objeto de definición de componente, un string con el nombre del componente o un string con el nombre de una etiqueta HTML. Vue lo resuelve en tiempo de ejecución y renderiza el componente o elemento correspondiente. Más allá del patrón estándar <component :is>, is tiene un comportamiento especial en elementos HTML nativos e interactúa con web components mediante el prefijo vue:.

Qué acepta :is ​

vue
<script setup>
import { shallowRef } from 'vue'
import AlertBox from './AlertBox.vue'
import InfoBox from './InfoBox.vue'

const currentComponent = shallowRef(AlertBox)
</script>

<template>
  <!-- 1. Objeto de definición de componente (recomendado) -->
  <component :is="currentComponent" />

  <!-- 2. Nombre de componente registrado (string) -->
  <component is="AlertBox" />

  <!-- 3. Nombre de elemento HTML (string) -->
  <component is="div" />

  <!-- 4. Función de render inline -->
  <component :is="() => h('span', 'hello')" />
</template>
Open in Vue Playground

Al usar una variable reactiva, usa shallowRef en lugar de ref. Un ref intentaría desempaquetar profundamente el objeto del componente, lo cual es innecesario y puede causar problemas con definiciones de componentes complejas.

is en elementos HTML nativos ​

El atributo is en elementos HTML normales se comporta de forma distinta que en <component>. Sigue la especificación HTML para elementos integrados personalizados:

vue
<!-- Comportamiento según la spec HTML: "is" en elementos nativos crea elementos integrados personalizados -->
<button is="my-custom-button">Click</button>
<!-- Esto le indica al navegador que actualice el <button> con una clase de custom element -->
Open in Vue Playground

Para renderizar un componente Vue como sustituto de un elemento nativo, usa el prefijo vue::

vue
<!-- Esto renderiza el componente Vue MyButton, no un <button> nativo -->
<button is="vue:MyButton">Click</button>

<!-- Útil cuando necesitas un componente Vue dentro de elementos con restricciones de hijos -->
<table>
  <tr is="vue:MyTableRow"></tr>
</table>
Open in Vue Playground

El problema con <table> ​

Las reglas de parseo HTML restringen qué elementos pueden aparecer dentro de <table>, <ul>, <ol> y <select>. El navegador mueve los hijos no válidos fuera de estos elementos antes de que Vue vea el DOM:

vue
<!-- MAL: el navegador mueve <BlogPost> fuera de <table> durante el parseo HTML -->
<table>
  <BlogPost />  <!-- acaba por encima de la tabla en el DOM -->
</table>

<!-- BIEN: usa is="vue:" para evitar la restricción -->
<table>
  <tr is="vue:BlogPost"></tr>
</table>
Open in Vue Playground

Esto solo es un problema cuando los templates se parsean como HTML (templates in-DOM). Los SFCs compilados con Vite no tienen este problema porque el template se compila en tiempo de build, no se parsea como HTML.

Renderizado dinámico con un mapa de componentes ​

Un patrón habitual para renderizar distintos componentes según los datos:

vue
<script setup>
import TextBlock from './TextBlock.vue'
import ImageBlock from './ImageBlock.vue'
import VideoBlock from './VideoBlock.vue'
import type { Component } from 'vue'

const blockComponents: Record<string, Component> = {
  text: TextBlock,
  image: ImageBlock,
  video: VideoBlock
}

const blocks = ref([
  { type: 'text', content: 'Hello' },
  { type: 'image', src: '/photo.jpg' },
  { type: 'text', content: 'World' },
  { type: 'video', src: '/clip.mp4' }
])
</script>

<template>
  <component
    v-for="(block, i) in blocks"
    :key="i"
    :is="blockComponents[block.type]"
    v-bind="block"
  />
</template>
Open in Vue Playground

Este patrón es más limpio que una cadena de v-if/v-else-if y escala a cualquier número de tipos de bloque sin modificar el template.

Orden de resolución ​

Cuando :is recibe un string, Vue lo resuelve en este orden:

  1. Componentes registrados localmente (mediante import en <script setup>)
  2. Componentes registrados globalmente (app.component('name', ...))
  3. Elementos HTML nativos (div, span, table, etc.)

Si el string no coincide con ningún componente ni elemento HTML, Vue no renderiza nada y emite un warning en desarrollo.

Combinado con KeepAlive y Transition ​

vue
<template>
  <KeepAlive :max="5">
    <Transition name="fade" mode="out-in">
      <component :is="currentTab" :key="currentTabName" />
    </Transition>
  </KeepAlive>
</template>
Open in Vue Playground

Añade :key al usar <Transition> para que Vue trate cada cambio de componente como una transición entre elementos distintos, en lugar de parchear el mismo componente.

Ver también: ¿Qué son los componentes dinámicos y KeepAlive? · ¿Cómo funcionan las render functions?

Referencias ​

Publicado bajo la licencia MIT.