Curso de Laravel
Componentes Blade en Laravel: vistas reutilizables
Por Víctor Peña · Publicado el
Hola, ¿cómo están? Continuando con el curso de Laravel, hoy veremos los componentes de Blade, que son la evolución natural de los @include que vimos en la lección anterior.
¡Empecemos!
El problema con los includes
Un @include funciona, pero tiene dos limitaciones que se notan enseguida.
Hereda todas las variables del contexto. No está claro qué necesita realmente:
@include('partials.alerta')
¿Qué variables espera ese archivo? Hay que abrirlo para saberlo. Y si olvidas definir una, el error aparece dentro del include.
No admite contenido variable. Si quieres una tarjeta cuyo interior cambie en cada uso, un include no te sirve.
Los componentes resuelven las dos cosas.
Crear un componente
php artisan make:component Alerta
Eso genera dos archivos:
app/View/Components/Alerta.php— la claseresources/views/components/alerta.blade.php— la plantilla
La clase:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class Alerta extends Component
{
public function __construct(
public string $tipo = 'info',
public ?string $titulo = null,
) {}
public function render(): View
{
return view('components.alerta');
}
}
La plantilla:
<div class="alerta alerta-{{ $tipo }}">
@if ($titulo)
<strong>{{ $titulo }}</strong>
@endif
{{ $slot }}
</div>
Y así se usa:
<x-alerta tipo="exito" titulo="Listo">
El paciente se registró correctamente.
</x-alerta>
Fíjate en tres cosas: el prefijo x-, que los parámetros del constructor se pasan como atributos HTML, y que el contenido entre las etiquetas llega en $slot.
Esa última es la diferencia con los includes: el componente define la estructura y quien lo usa decide el contenido.
Pasar datos
Los valores fijos se pasan como atributos normales:
<x-alerta tipo="error" />
Y las variables con dos puntos delante:
<x-alerta :tipo="$estado" :titulo="$paciente->nombre" />
Ese : significa «esto es una expresión PHP, no un texto». Es el error más frecuente al empezar:
<x-tarjeta paciente="$paciente" /> {{-- pasa el texto "$paciente" --}}
<x-tarjeta :paciente="$paciente" /> {{-- pasa el objeto --}}
Componentes anónimos
Cuando el componente no necesita lógica, no hace falta la clase:
php artisan make:component boton --view
Solo se crea resources/views/components/boton.blade.php:
@props([
'tipo' => 'primario',
'href' => null,
])
@if ($href)
<a href="{{ $href }}" {{ $attributes->merge(['class' => "boton boton-$tipo"]) }}>
{{ $slot }}
</a>
@else
<button {{ $attributes->merge(['class' => "boton boton-$tipo"]) }}>
{{ $slot }}
</button>
@endif
La directiva @props declara qué recibe el componente y con qué valores por defecto.
Los componentes anónimos son los que más vas a escribir. La mayoría de piezas de interfaz —botones, tarjetas, campos de formulario— no necesitan lógica.
El objeto $attributes
Aquí está una de las funcionalidades más útiles y menos conocidas.
Los atributos que no declaraste en @props se recogen en $attributes, y puedes reenviarlos al HTML:
<x-boton tipo="peligro" id="eliminar" data-confirmar="true">
Eliminar
</x-boton>
En la plantilla, {{ $attributes }} imprime id="eliminar" data-confirmar="true".
Y merge() combina tus clases con las que pase quien lo use:
{{ $attributes->merge(['class' => 'boton']) }}
<x-boton class="ancho-completo">Guardar</x-boton>
{{-- resultado: class="boton ancho-completo" --}}
Otros métodos útiles:
{{ $attributes->class(['boton', 'boton-activo' => $activo]) }}
{{ $attributes->except(['tipo']) }}
{{ $attributes->only(['id', 'name']) }}
Slots con nombre
Cuando el componente tiene varias zonas de contenido:
{{-- components/tarjeta.blade.php --}}
<div class="tarjeta">
<div class="tarjeta-cabecera">
{{ $cabecera }}
</div>
<div class="tarjeta-cuerpo">
{{ $slot }}
</div>
@isset($pie)
<div class="tarjeta-pie">
{{ $pie }}
</div>
@endisset
</div>
<x-tarjeta>
<x-slot:cabecera>
<h2>{{ $paciente->nombre }}</h2>
</x-slot:cabecera>
<p>Cédula: {{ $paciente->cedula }}</p>
<p>Teléfono: {{ $paciente->telefono }}</p>
<x-slot:pie>
<a href="{{ route('pacientes.edit', $paciente) }}">Editar</a>
</x-slot:pie>
</x-tarjeta>
El @isset($pie) hace que el pie solo aparezca si se lo pasaron.
El caso que más rentabiliza: campos de formulario
Este es el ejemplo donde los componentes ahorran más código real.
{{-- components/campo.blade.php --}}
@props([
'nombre',
'etiqueta',
'tipo' => 'text',
'valor' => null,
'requerido' => false,
])
<div class="campo">
<label for="{{ $nombre }}">
{{ $etiqueta }}
@if ($requerido)<span class="obligatorio">*</span>@endif
</label>
<input
type="{{ $tipo }}"
id="{{ $nombre }}"
name="{{ $nombre }}"
value="{{ old($nombre, $valor) }}"
@if ($requerido) required @endif
{{ $attributes->class(['campo-entrada', 'campo-error' => $errors->has($nombre)]) }}
>
@error($nombre)
<span class="mensaje-error">{{ $message }}</span>
@enderror
</div>
Y el formulario completo queda así:
<form method="POST" action="{{ route('pacientes.store') }}">
@csrf
<x-campo nombre="cedula" etiqueta="Cédula" requerido />
<x-campo nombre="nombre" etiqueta="Nombre" requerido />
<x-campo nombre="apellido" etiqueta="Apellido" requerido />
<x-campo nombre="fecha_nacimiento" etiqueta="Fecha de nacimiento" tipo="date" requerido />
<x-campo nombre="correo" etiqueta="Correo" tipo="email" />
<x-boton type="submit">Guardar</x-boton>
</form>
Compáralo con escribir a mano la etiqueta, el input, el old() y el @error cinco veces. Y si mañana cambias el diseño de los campos, lo cambias en un archivo.
Componentes en subcarpetas
resources/views/components/
├── formulario/
│ ├── campo.blade.php
│ └── select.blade.php
└── ui/
├── boton.blade.php
└── tarjeta.blade.php
Se usan con punto:
<x-formulario.campo nombre="cedula" etiqueta="Cédula" />
<x-ui.boton>Guardar</x-ui.boton>
En proyectos que crecen, esa organización se agradece.
El layout como componente
Es la alternativa moderna al @extends que vimos antes:
{{-- components/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="es">
<head>
<title>{{ $titulo ?? 'Sistema Clínica' }}</title>
@vite(['resources/css/app.css'])
</head>
<body>
<header>...</header>
<main>{{ $slot }}</main>
</body>
</html>
<x-layouts.app titulo="Pacientes">
<h1>Listado de pacientes</h1>
...
</x-layouts.app>
Los dos enfoques son válidos. @extends es el clásico y lo verás en la mayoría de proyectos existentes; el componente es más consistente con el resto de Blade. Elige uno y sé coherente en todo el proyecto.
Cuándo componente y cuándo include
| Situación | Usa |
|---|---|
| El contenido interior cambia | Componente |
| Recibe parámetros claros | Componente |
| Se usa en muchos sitios | Componente |
| Un fragmento fijo, usado en dos vistas | @include |
| Un formulario compartido entre crear y editar | @include |
En general, componente por defecto. El include queda para fragmentos que dependen del contexto y no valen la pena parametrizar.
Errores comunes
- Olvidar los dos puntos al pasar una variable, y enviar el texto literal.
- No declarar
@props, y que el componente reciba variables por accidente. - No usar
$attributes->merge(), obligando a modificar el componente cada vez que hace falta una clase. - Componentes que hacen consultas. Los datos vienen del controlador.
- Mezclar
@extendsy layout-componente en el mismo proyecto.
Para cerrar
Los componentes son a las vistas lo que las funciones son al código: una forma de escribir una vez y usar muchas.
El consejo práctico: empieza por un componente de campo de formulario. Es donde más repetición hay en cualquier proyecto, y en cuanto lo tengas no vas a querer volver atrás.
En la siguiente lección veremos la validación, que encaja directamente con esos campos.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp