Laravel Blade 模板引擎完全指南:从基础语法到高级组件
Orion K Lv6

Blade 是 Laravel 自带的简单而强大的模板引擎。与其他 PHP 模板引擎不同,Blade 不限制你在模板中使用纯 PHP 代码,同时提供了优雅的语法和强大的功能。本文将全面介绍 Blade 模板引擎的使用方法和最佳实践。

Blade 模板引擎简介

核心特性

  1. 零开销:所有 Blade 模板都会被编译成纯 PHP 代码并缓存
  2. 简洁语法:提供清晰、易读的模板语法
  3. 模板继承:支持强大的布局继承系统
  4. 组件系统:现代化的组件开发方式
  5. 安全性:自动防止 XSS 攻击

文件结构

Blade 模板文件使用 .blade.php 扩展名,通常存储在 resources/views 目录中:

1
2
3
4
5
6
7
8
9
10
11
12
13
resources/views/
├── layouts/
│ ├── app.blade.php
│ └── guest.blade.php
├── components/
│ ├── button.blade.php
│ └── card.blade.php
├── pages/
│ ├── home.blade.php
│ └── about.blade.php
└── partials/
├── header.blade.php
└── footer.blade.php

基础语法

显示数据

1
2
3
4
5
6
7
8
9
10
11
{{-- 基本数据输出(自动转义) --}}
<h1>Hello, {{ $name }}!</h1>

{{-- 输出 PHP 函数结果 --}}
<p>当前时间:{{ date('Y-m-d H:i:s') }}</p>

{{-- 三元运算符 --}}
<p>用户状态:{{ $user->isActive() ? '活跃' : '非活跃' }}</p>

{{-- 空值合并 --}}
<p>用户名:{{ $user->name ?? '游客' }}</p>

原始数据输出

1
2
3
4
5
6
7
8
9
{{-- 输出未转义的 HTML(谨慎使用) --}}
<div class="content">
{!! $htmlContent !!}
</div>

{{-- 在 JavaScript 中使用 --}}
<script>
var config = {!! json_encode($config) !!};
</script>

与 JavaScript 框架兼容

1
2
3
4
5
6
7
8
9
10
11
12
{{-- 使用 @ 符号避免 Blade 解析 --}}
<div id="app">
<h1>Laravel</h1>
<p>Hello, @{{ name }}!</p> {{-- Vue.js 语法 --}}
</div>

{{-- 使用 @verbatim 指令 --}}
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim

Blade 指令

条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{{-- if 语句 --}}
@if ($user->isAdmin())
<p>欢迎,管理员!</p>
@elseif ($user->isModerator())
<p>欢迎,版主!</p>
@else
<p>欢迎,普通用户!</p>
@endif

{{-- unless 语句(if 的反向) --}}
@unless ($user->isGuest())
<p>你已登录</p>
@endunless

{{-- issetempty 检查 --}}
@isset($records)
<p>记录存在</p>
@endisset

@empty($records)
<p>没有记录</p>
@endempty

{{-- 认证检查 --}}
@auth
<p>用户已认证</p>
@endauth

@guest
<p>用户未认证</p>
@endguest

{{-- 环境检查 --}}
@production
<script src="{{ asset('js/app.min.js') }}"></script>
@endproduction

@env('local')
<script src="{{ asset('js/app.js') }}"></script>
@endenv

循环语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{{-- foreach 循环 --}}
@foreach ($users as $user)
<div class="user-card">
<h3>{{ $user->name }}</h3>
<p>{{ $user->email }}</p>
</div>
@endforeach

{{-- forelse 循环(带空值处理) --}}
@forelse ($posts as $post)
<article>
<h2>{{ $post->title }}</h2>
<p>{{ $post->excerpt }}</p>
</article>
@empty
<p>暂无文章</p>
@endforelse

{{-- for 循环 --}}
@for ($i = 0; $i < 10; $i++)
<p>当前值:{{ $i }}</p>
@endfor

{{-- while 循环 --}}
@while (true)
<p>这是一个无限循环</p>
@break
@endwhile

循环变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@foreach ($users as $user)
<div class="user-item {{ $loop->first ? 'first' : '' }} {{ $loop->last ? 'last' : '' }}">
<span class="index">{{ $loop->iteration }}</span>
<span class="name">{{ $user->name }}</span>

@if ($loop->remaining > 0)
<span class="remaining">还有 {{ $loop->remaining }} 个用户</span>
@endif

{{-- 嵌套循环 --}}
@foreach ($user->posts as $post)
<div class="post {{ $loop->parent->first ? 'first-user-post' : '' }}">
{{ $post->title }}
</div>
@endforeach
</div>
@endforeach

Switch 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@switch($user->role)
@case('admin')
<p>管理员权限</p>
@break

@case('moderator')
<p>版主权限</p>
@break

@case('user')
<p>普通用户权限</p>
@break

@default
<p>未知权限</p>
@endswitch

模板继承

定义布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">

<title>@yield('title', config('app.name'))</title>

{{-- 样式文件 --}}
@vite(['resources/css/app.css'])
@stack('styles')
</head>
<body>
<div id="app">
{{-- 导航栏 --}}
@include('partials.navbar')

{{-- 主要内容区域 --}}
<main class="main-content">
@yield('content')
</main>

{{-- 侧边栏(可选) --}}
@hasSection('sidebar')
<aside class="sidebar">
@yield('sidebar')
</aside>
@endif

{{-- 页脚 --}}
@include('partials.footer')
</div>

{{-- JavaScript 文件 --}}
@vite(['resources/js/app.js'])
@stack('scripts')
</body>
</html>

继承布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')

@section('title', '文章列表')

@push('styles')
<link rel="stylesheet" href="{{ asset('css/posts.css') }}">
@endpush

@section('content')
<div class="container">
<h1>文章列表</h1>

@forelse ($posts as $post)
<article class="post-card">
<h2><a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a></h2>
<p class="meta">
作者:{{ $post->author->name }} |
发布时间:{{ $post->created_at->format('Y-m-d') }}
</p>
<p class="excerpt">{{ $post->excerpt }}</p>
</article>
@empty
<p class="no-posts">暂无文章</p>
@endforelse

{{ $posts->links() }}
</div>
@endsection

@section('sidebar')
<div class="widget">
<h3>热门标签</h3>
<div class="tags">
@foreach ($popularTags as $tag)
<a href="{{ route('tags.show', $tag) }}" class="tag">{{ $tag->name }}</a>
@endforeach
</div>
</div>
@endsection

@push('scripts')
<script src="{{ asset('js/posts.js') }}"></script>
@endpush

组件系统

创建组件

1
2
3
4
5
# 创建组件类和视图
php artisan make:component Button

# 创建匿名组件
php artisan make:component forms.input --view

组件类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
// app/View/Components/Button.php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Button extends Component
{
/**
* 按钮类型
*/
public string $type;

/**
* 按钮大小
*/
public string $size;

/**
* 是否禁用
*/
public bool $disabled;

/**
* 创建组件实例
*/
public function __construct(
string $type = 'button',
string $size = 'md',
bool $disabled = false
) {
$this->type = $type;
$this->size = $size;
$this->disabled = $disabled;
}

/**
* 获取按钮的 CSS 类
*/
public function classes(): string
{
$classes = ['btn'];

// 根据类型添加类
$classes[] = match($this->type) {
'primary' => 'btn-primary',
'secondary' => 'btn-secondary',
'danger' => 'btn-danger',
default => 'btn-default'
};

// 根据大小添加类
$classes[] = match($this->size) {
'sm' => 'btn-sm',
'lg' => 'btn-lg',
default => 'btn-md'
};

if ($this->disabled) {
$classes[] = 'btn-disabled';
}

return implode(' ', $classes);
}

/**
* 渲染组件
*/
public function render(): View
{
return view('components.button');
}
}

组件视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{{-- resources/views/components/button.blade.php --}}
@props([
'type' => 'button',
'href' => null,
'disabled' => false
])

@if($href)
<a href="{{ $href }}" {{ $attributes->merge(['class' => $classes()]) }}>
{{ $slot }}
</a>
@else
<button
type="{{ $type }}"
{{ $disabled ? 'disabled' : '' }}
{{ $attributes->merge(['class' => $classes()]) }}
>
{{ $slot }}
</button>
@endif

使用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{{-- 基本使用 --}}
<x-button type="primary">保存</x-button>

{{-- 传递属性 --}}
<x-button
type="danger"
size="lg"
:disabled="$user->cannot('delete', $post)"
onclick="confirmDelete()"
>
删除文章
</x-button>

{{-- 作为链接 --}}
<x-button
type="secondary"
:href="route('posts.edit', $post)"
>
编辑
</x-button>

{{-- 使用插槽 --}}
<x-button type="primary">
<x-icon name="save" />
保存文章
</x-button>

匿名组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{{-- resources/views/components/forms/input.blade.php --}}
@props([
'type' => 'text',
'name',
'label' => null,
'required' => false,
'error' => null
])

<div class="form-group">
@if($label)
<label for="{{ $name }}" class="form-label">
{{ $label }}
@if($required)
<span class="text-red-500">*</span>
@endif
</label>
@endif

<input
type="{{ $type }}"
name="{{ $name }}"
id="{{ $name }}"
{{ $required ? 'required' : '' }}
{{ $attributes->merge([
'class' => 'form-input' . ($error ? ' border-red-500' : '')
]) }}
value="{{ old($name, $attributes->get('value')) }}"
>

@if($error)
<p class="text-red-500 text-sm mt-1">{{ $error }}</p>
@endif
</div>

组件插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{{-- 组件定义 --}}
{{-- resources/views/components/card.blade.php --}}
<div class="card">
@isset($header)
<div class="card-header">
{{ $header }}
</div>
@endisset

<div class="card-body">
{{ $slot }}
</div>

@isset($footer)
<div class="card-footer">
{{ $footer }}
</div>
@endisset
</div>

{{-- 使用组件 --}}
<x-card>
<x-slot:header>
<h3>用户信息</h3>
</x-slot:header>

<p>这是卡片的主要内容</p>
<p>用户名:{{ $user->name }}</p>

<x-slot:footer>
<x-button type="primary">编辑</x-button>
<x-button type="secondary">取消</x-button>
</x-slot:footer>
</x-card>

高级特性

包含子视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{{-- 基本包含 --}}
@include('partials.navbar')

{{-- 传递数据 --}}
@include('partials.user-card', ['user' => $user])

{{-- 条件包含 --}}
@includeIf('partials.admin-panel')

{{-- 根据条件包含不同视图 --}}
@includeWhen($user->isAdmin(), 'partials.admin-panel')
@includeUnless($user->isGuest(), 'partials.user-menu')

{{-- 包含第一个存在的视图 --}}
@includeFirst(['partials.custom-header', 'partials.default-header'])

堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{{-- 在布局中定义堆栈 --}}
@stack('styles')
@stack('scripts')

{{-- 在子视图中推送内容 --}}
@push('styles')
<link rel="stylesheet" href="{{ asset('css/custom.css') }}">
@endpush

@push('scripts')
<script src="{{ asset('js/custom.js') }}"></script>
@endpush

{{-- 前置推送 --}}
@prepend('styles')
<link rel="stylesheet" href="{{ asset('css/critical.css') }}">
@endprepend

服务注入

1
2
3
4
5
6
7
8
{{-- 注入服务 --}}
@inject('metrics', 'App\Services\MetricsService')

<div class="dashboard">
<h1>仪表板</h1>
<p>总用户数:{{ $metrics->getTotalUsers() }}</p>
<p>今日访问量:{{ $metrics->getTodayVisits() }}</p>
</div>

@once 指令

1
2
3
4
5
6
{{-- 确保代码只执行一次 --}}
@once
@push('scripts')
<script src="{{ asset('js/chart.js') }}"></script>
@endpush
@endonce

原始 PHP

1
2
3
4
5
6
7
{{-- 使用原始 PHP 代码 --}}
@php
$isWeekend = date('N') >= 6;
$greeting = $isWeekend ? '周末愉快' : '工作日快乐';
@endphp

<h1>{{ $greeting }}!</h1>

表单处理

CSRF 保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<form method="POST" action="{{ route('posts.store') }}">
@csrf

<x-forms.input
name="title"
label="标题"
:required="true"
:error="$errors->first('title')"
/>

<x-forms.textarea
name="content"
label="内容"
:required="true"
:error="$errors->first('content')"
/>

<x-button type="submit">发布文章</x-button>
</form>

方法伪造

1
2
3
4
5
6
<form method="POST" action="{{ route('posts.update', $post) }}">
@csrf
@method('PUT')

{{-- 表单字段 --}}
</form>

验证错误显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{{-- 显示所有错误 --}}
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

{{-- 显示特定字段错误 --}}
@error('email')
<div class="text-red-500 text-sm">{{ $message }}</div>
@enderror

性能优化

视图缓存

1
2
3
4
5
# 缓存视图
php artisan view:cache

# 清除视图缓存
php artisan view:clear

组件缓存

1
2
3
4
5
6
7
8
// 在 AppServiceProvider 中
public function boot()
{
// 在生产环境中缓存组件
if (app()->environment('production')) {
Blade::componentNamespace('App\\View\\Components', 'app');
}
}

最佳实践

  1. 保持模板简洁:避免在模板中编写复杂的业务逻辑
  2. 合理使用组件:将可重用的 UI 元素封装成组件
  3. 遵循命名约定:使用清晰、一致的文件和变量命名
  4. 安全第一:始终使用 {{ }} 输出用户数据,避免 XSS 攻击
  5. 性能考虑:在生产环境中启用视图缓存
  6. 代码复用:使用 @include 和组件避免重复代码

总结

Blade 模板引擎是 Laravel 框架的重要组成部分,它提供了强大而优雅的模板解决方案。通过掌握 Blade 的基础语法、模板继承、组件系统和高级特性,你可以构建出结构清晰、易于维护的前端界面。合理使用 Blade 的各种功能,将大大提高你的开发效率和代码质量。

本站由 提供部署服务