Views do Laravel e o Motor de Template Blade
Blade é o "motor de layout" do Laravel — é aqui que os dados são transformados nas páginas que os usuários veem, e onde layouts, componentes e herança eliminam código de página repetitivo.
1. O Que Você Vai Aprender
- Herança de layout Blade: @extends, @section, @yield
- Estruturas de Controle: @if/@foreach/@switch/@auth/@guest
- Componentes Blade: Componentes de Classe vs. Componentes Anônimos
- View Composers: Compartilhando Dados
- Assets de front-end: Vite compila Tailwind CSS / Alpine.js
2. Uma História Real de um Desenvolvedor Front-End
(1) Dor: Ter que escrever a barra de navegação repetidamente em cada página
Charlie escreveu 10 páginas para o ShopMetrics, copiando manualmente 60 linhas de HTML da barra de navegação em cada uma. Quando Bob pediu para mover o link "Preços" da barra de navegação para um menu suspenso, Charlie teve que editar todos os 10 arquivos um por um — mas quando chegou ao sétimo arquivo, perdeu uma classe, causando inconsistência nos estilos da barra de navegação no site ao vivo, o que gerou três reclamações da Alice.
(2) Solução para o Layout Blade
O Blade usa @extends para herdar o layout e @section para preencher o conteúdo; a barra de navegação é definida apenas uma vez e é sincronizada automaticamente em todo o site.
// resources/views/layouts/app.blade.php — Definir layout uma vez
<body>
<nav>...</nav> <!-- Navegação aparece em cada página -->
@yield('content') <!-- Cada página preenche este slot -->
</body>
// resources/views/shops/index.blade.php — Estender e preencher
@extends('layouts.app')
@section('content')
<h1>Todas as Lojas</h1>
@endsection
(3) Resultado
Depois que Charlie implementou o layout Blade, a barra de navegação exigiu manutenção de apenas um único arquivo. O pedido de mudança do Bob passou de "modificar 10 arquivos" para "modificar 1 arquivo", com zero omissões.
3. Herança de Layout Blade
(1) Definindo o Layout
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>@yield('title', 'ShopMetrics')</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
@include('partials.navbar')
<main class="container mx-auto">
@yield('content')
</main>
@include('partials.footer')
</body>
</html>
(2) Herdando Layouts
<!-- resources/views/shops/index.blade.php -->
@extends('layouts.app')
@section('title', 'Todas as Lojas')
@section('content')
<div class="grid grid-cols-3 gap-4">
@foreach($shops as $shop)
<div class="card">{{ $shop->name }}</div>
@endforeach
</div>
@endsection
| Comando | Função | Analogia |
|---|---|---|
@extends |
Especificar o layout a herdar | Selecionar um modelo |
@section |
Definir blocos de conteúdo | Preencher os espaços do modelo |
@yield |
Emitir bloco de conteúdo | Espaço reservado no modelo |
@include |
Importar Subvisão | Inserir Snippet Público |
(1) ▶ Exemplo: Layout de Duas Colunas do ShopMetrics
<!-- resources/views/layouts/dashboard.blade.php -->
@extends('layouts.app')
@section('content')
<div class="flex">
@include('partials.sidebar')
<div class="flex-1 p-6">
@yield('dashboard-content')
</div>
</div>
@endsection
<!-- resources/views/dashboard/analytics.blade.php -->
@extends('layouts.dashboard')
@section('title', 'Painel de Análise')
@section('dashboard-content')
<h1>Visão Geral de Análise</h1>
<div id="chart"></div>
@endsection
Saída:
// Execução Bem-Sucedida
4. Estruturas de Controle
(1) Declarações Condicionais
@if($shop->is_active)
<span class="badge-green">Ativo</span>
@elseif($shop->is_suspended)
<span class="badge-yellow">Suspenso</span>
@else
<span class="badge-gray">Inativo</span>
@endif
@auth
<a href="{{ route('dashboard') }}">Painel</a>
@endauth
@guest
<a href="{{ route('login') }}">Login</a>
@endguest
(2) Declarações de Loop
@foreach($shops as $shop)
<tr>
<td>{{ $shop->name }}</td>
<td>{{ $shop->revenue }}</td>
</tr>
@if($loop->last)
</tbody></table>
@endif
@endforeach
@forelse($orders as $order)
<li>{{ $order->total }}</li>
@empty
<li>Nenhum pedido ainda.</li>
@endforelse
| Variável do Loop | Descrição |
|---|---|
$loop->index |
Índice Atual (começando em 0) |
$loop->iteration |
Rodada Atual (começando em 1) |
$loop->first |
É o primeiro? |
$loop->last |
É o último? |
$loop->count |
Total |
(1) ▶ Exemplo: Renderização Condicional no Painel do ShopMetrics
<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')
@section('dashboard-content')
@auth
<h1>Bem-vindo, {{ auth()->user()->name }}!</h1>
@endauth
@if($shops->count() > 0)
<div class="grid grid-cols-3 gap-4">
@foreach($shops as $shop)
<div class="card {{ $loop->first ? 'border-blue-500' : '' }}">
<h3>{{ $shop->name }}</h3>
<p>Receita: ${{ number_format($shop->revenue, 2) }}</p>
</div>
@endforeach
</div>
@else
<p>Nenhuma loja ainda. <a href="{{ route('shops.create') }}">Crie uma!</a></p>
@endif
@endsection
Saída:
// Execução Bem-Sucedida
5. Componente Blade
(1) Componentes Anônimos
<!-- resources/views/components/shop-card.blade.php -->
@props(['shop', 'highlight' => false])
<div class="card {{ $highlight ? 'border-gold' : '' }}">
<h3>{{ $shop->name }}</h3>
<p>{{ $shop->description }}</p>
{{ $slot }}
</div>
<!-- Uso -->
<x-shop-card :shop="$shop" :highlight="true">
<p>Destaque desta semana!</p>
</x-shop-card>
(2) Componentes de Classe
php artisan make:component Alert
# Cria: app/View/Components/Alert.php
# E: resources/views/components/alert.blade.php
// app/View/Components/Alert.php
class Alert extends Component
{
public function __construct(
public string $type = 'info',
public ?string $message = null,
) {}
public function render(): View
{
return view('components.alert');
}
}
<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type }}">
{{ $message ?? $slot }}
</div>
<!-- Uso -->
<x-alert type="success" message="Loja criada!" />
<x-alert type="warning">Verifique suas configurações.</x-alert>
| Dimensão | Anônimo | Classe |
|---|---|---|
| Arquivo | Apenas Templates Blade | Classes PHP + Templates Blade |
| Lógica | Nenhuma/Mínima | Pode incluir lógica de negócio |
| Props | Declaração @props() |
Parâmetros do Construtor |
| Adequado para | Componentes de exibição simples | Componentes que exigem lógica |
(1) ▶ Exemplo: Componente de Cartão de Estatísticas do ShopMetrics
<!-- resources/views/components/stat-card.blade.php -->
@props(['label', 'value', 'icon', 'trend' => null])
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">{{ $label }}</p>
<p class="text-2xl font-bold">{{ $value }}</p>
@if($trend)
<p class="text-sm {{ $trend > 0 ? 'text-green-500' : 'text-red-500' }}">
{{ $trend > 0 ? '+' : '' }}{{ $trend }}%
</p>
@endif
</div>
<span class="text-3xl">{{ $icon }}</span>
</div>
</div>
<!-- Uso no painel -->
<x-stat-card label="Receita Total" value="$12,450" icon="$" :trend="15.3" />
<x-stat-card label="Lojas Ativas" value="23" icon="#" :trend="-2.1" />
Saída:
// Execução Bem-Sucedida
6. View Composer
Os View Composers injetam automaticamente dados compartilhados nas views, eliminando a necessidade de passá-los repetidamente em cada método do controller.
// app/Providers/AppServiceProvider.php
public function boot(): void
{
// Compartilhar informações do tenant com todas as views do painel
View::composer('dashboard.*', function ($view) {
$view->with('tenant', Tenant::current());
});
// Compartilhar dados de navegação com views específicas
View::composer(['layouts.app', 'shops.*'], NavigationComposer::class);
}
| Tipo | Quando Executar | Propósito |
|---|---|---|
View::composer() |
Cada vez que a view é renderizada | Calcular dados dinamicamente |
View::creator() |
Ao instanciar uma view | Vincular dados mais cedo |
View::share() |
Todas as Views | Constantes Globais |
(1) ▶ Exemplo: View Composer de Navegação do ShopMetrics
// app/View/Composers/NavigationComposer.php
class NavigationComposer
{
public function compose(View $view): void
{
$view->with([
'navShops' => auth()->check()
? auth()->user()->shops()->take(5)->get()
: collect(),
'navNotifications' => auth()->check()
? auth()->user()->unreadNotifications()->count()
: 0,
]);
}
}
// Registrar no AppServiceProvider
View::composer('layouts.app', NavigationComposer::class);
Saída:
// Execução Bem-Sucedida
7. Assets de Front-End: Vite
O Laravel 11 usa o Vite por padrão para compilar assets de front-end.
(1) Instalar Tailwind CSS e Alpine.js
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install alpinejs
(2) Configurar Vite
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
],
});
(3) Introdução ao Blade
<!-- No head do layout -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
| Comando | Função |
|---|---|
npm run dev |
Iniciar o servidor de desenvolvimento Vite (hot reload) |
npm run build |
Compilar Assets de Produção (Comprimido) |
(1) ▶ Exemplo: Navegação do ShopMetrics com Interações Alpine.js
<!-- resources/views/partials/navbar.blade.php -->
<nav class="bg-white shadow" x-data="{ open: false }">
<div class="container mx-auto px-4">
<div class="flex justify-between h-16">
<a href="{{ route('home') }}" class="font-bold text-xl">ShopMetrics</a>
<div class="flex items-center">
@auth
<div class="relative" @click="open = !open">
<button class="flex items-center">
{{ auth()->user()->name }}
@if($navNotifications > 0)
<span class="badge-red">{{ $navNotifications }}</span>
@endif
</button>
<div x-show="open" x-transition class="dropdown">
<a href="{{ route('dashboard') }}">Painel</a>
<a href="{{ route('shops.index') }}">Lojas</a>
<form action="{{ route('logout') }}" method="POST">
@csrf
<button type="submit">Sair</button>
</form>
</div>
</div>
@else
<a href="{{ route('login') }}">Login</a>
@endauth
</div>
</div>
</div>
</nav>
Saída:
// Execução Bem-Sucedida
8. Exemplo Abrangente: Página do Painel do ShopMetrics
<!-- ============================================
Abrangente: Página do Painel do ShopMetrics
Conteúdo: herança de layout, componentes, diretivas, Alpine.js
============================================ -->
<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')
@section('title', 'Painel — ShopMetrics')
@section('dashboard-content')
<div class="space-y-6" x-data="{ period: '7d' }">
<!-- Seletor de Período -->
<div class="flex gap-2">
<button @click="period = '7d'"
:class="period === '7d' ? 'btn-primary' : 'btn-secondary'">7 Dias</button>
<button @click="period = '30d'"
:class="period === '30d' ? 'btn-primary' : 'btn-secondary'">30 Dias</button>
<button @click="period = '90d'"
:class="period === '90d' ? 'btn-primary' : 'btn-secondary'">90 Dias</button>
</div>
<!-- Cartões de Estatísticas -->
<div class="grid grid-cols-4 gap-4">
<x-stat-card label="Receita Total" value="${{ number_format($totalRevenue, 0) }}" icon="$" :trend="$revenueTrend" />
<x-stat-card label="Pedidos" value="{{ number_format($orderCount) }}" icon="#" :trend="$orderTrend" />
<x-stat-card label="Lojas Ativas" value="{{ $activeShops }}" icon="#" :trend="$shopTrend" />
<x-stat-card label="Taxa de Conversão" value="{{ number_format($conversionRate, 1) }}%" icon="%" :trend="$conversionTrend" />
</div>
<!-- Pedidos Recentes -->
<div class="bg-white rounded-lg shadow">
<h2 class="p-4 border-b font-semibold">Pedidos Recentes</h2>
@forelse($recentOrders as $order)
<div class="p-4 border-b flex justify-between">
<span>{{ $order->product_name }}</span>
<span>${{ number_format($order->total, 2) }}</span>
</div>
@empty
<p class="p-4 text-gray-500">Nenhum pedido neste período.</p>
@endforelse
</div>
</div>
@endsection
❓ Perguntas Frequentes
{{ dd($variable) }} para despejar e pausar a página diretamente; ou instale a extensão Laravel Debugbar para visualizar todas as variáveis, consultas SQL e uso de memória.view('xxx', ['key' => $value]), adequado para dados específicos daquela view; View Composers injetam automaticamente dados compartilhados, adequados para dados necessários por múltiplas views (como barra de navegação ou número de notificações).📖 Resumo
- Blade usa @extends, @section e @yield para implementar herança de layout e eliminar HTML duplicado
- Diretivas de controle @if, @foreach, @auth e @guest são usadas para lidar com lógica condicional em templates
- Componentes Anônimos são adequados para componentes simples, enquanto Componentes de Classe são adequados para componentes com lógica
- View Composers injetam automaticamente dados compartilhados nas views, eliminando a necessidade de controllers passarem valores repetidamente
- Vite é a ferramenta de build frontend padrão do Laravel 11 e suporta hot reloading
- {{ }} escapa caracteres automaticamente para prevenir XSS; use {!! !!} com cautela ao emitir HTML bruto
📝 Exercícios
-
Exercício Básico (⭐): Crie um arquivo de layout
layouts/app.blade.phppara o ShopMetrics que inclua uma barra de navegação e rodapé, depois crie uma página inicialhome/index.blade.phpque herde deste layout e preencha com conteúdo. -
Exercício Avançado (⭐⭐): Crie um componente Blade
<x-alert>que suporte umtype(success/warning/error) e conteúdo de slot, para ser usado em cenários do ShopMetrics onde a criação é bem-sucedida ou a validação falha. -
Desafio (⭐⭐⭐): Use Alpine.js para implementar um componente de barra lateral colapsável, integre-o em um layout de painel, suporte colapso automático em dispositivos móveis e salve o estado no localStorage.



