模板继承与组件 #

一、模板继承 #

1.1 概述 #

模板继承允许你定义一个基础布局,然后在子模板中扩展它。

text
模板继承结构
├── 布局模板(父模板)
│   ├── @yield 定义占位区
│   └── @section 定义区块
└── 子模板
    ├── @extends 继承布局
    └── @section 填充区块

1.2 定义布局 #

blade
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'Laravel')</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    @stack('styles')
</head>
<body>
    @include('partials.header')

    <main class="container">
        @yield('content')
    </main>

    @include('partials.footer')

    <script src="{{ asset('js/app.js') }}"></script>
    @stack('scripts')
</body>
</html>

1.3 继承布局 #

blade
<!-- resources/views/users/index.blade.php -->
@extends('layouts.app')

@section('title', '用户列表')

@section('content')
    <h1>用户列表</h1>
    <table>
        @foreach($users as $user)
            <tr>
                <td>{{ $user->name }}</td>
            </tr>
        @endforeach
    </table>
@endsection

二、@yield与@section #

2.1 @yield #

@yield 定义一个占位区,用于显示内容:

blade
<!-- 布局中 -->
<title>@yield('title')</title>

<!-- 带默认值 -->
<title>@yield('title', '默认标题')</title>

2.2 @section #

@section 有两种用法:

1. 在布局中定义区块

blade
<!-- 布局中 -->
@section('sidebar')
    <div class="sidebar">
        默认侧边栏内容
    </div>
@show

2. 在子模板中填充区块

blade
<!-- 子模板中 -->
@section('sidebar')
    @parent
    <div class="sidebar-extra">
        额外内容
    </div>
@endsection

2.3 @parent指令 #

@parent 用于在子模板中保留父模板的内容:

blade
<!-- 布局中 -->
@section('scripts')
    <script src="/base.js"></script>
@show

<!-- 子模板中 -->
@section('scripts')
    @parent
    <script src="/page.js"></script>
@endsection

<!-- 最终输出 -->
<script src="/base.js"></script>
<script src="/page.js"></script>

2.4 覆盖区块 #

不使用 @parent 会完全覆盖父模板内容:

blade
<!-- 布局中 -->
@section('sidebar')
    <p>默认侧边栏</p>
@show

<!-- 子模板中 -->
@section('sidebar')
    <p>完全替换</p>
@endsection

<!-- 最终输出 -->
<p>完全替换</p>

三、多级继承 #

3.1 基础布局 #

blade
<!-- resources/views/layouts/base.blade.php -->
<!DOCTYPE html>
<html>
<head>
    <title>@yield('title')</title>
</head>
<body>
    @yield('content')
</body>
</html>

3.2 中间布局 #

blade
<!-- resources/views/layouts/app.blade.php -->
@extends('layouts.base')

@section('content')
    <div class="app-container">
        @yield('app-content')
    </div>
@endsection

3.3 最终页面 #

blade
<!-- resources/views/home.blade.php -->
@extends('layouts.app')

@section('title', '首页')

@section('app-content')
    <h1>欢迎</h1>
@endsection

四、Blade组件 #

4.1 创建组件 #

bash
php artisan make:component Alert

生成的文件:

text
app/View/Components/Alert.php
resources/views/components/alert.blade.php

4.2 组件类 #

php
// app/View/Components/Alert.php
namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    public $type;
    public $message;

    public function __construct($type = 'info', $message = '')
    {
        $this->type = $type;
        $this->message = $message;
    }

    public function render()
    {
        return view('components.alert');
    }
}

4.3 组件视图 #

blade
<!-- resources/views/components/alert.blade.php -->
<div {{ $attributes->merge(['class' => 'alert alert-' . $type]) }}>
    {{ $message }}
    {{ $slot }}
</div>

4.4 使用组件 #

blade
<x-alert type="success" message="操作成功!" />

<x-alert type="danger">
    <strong>错误!</strong> 操作失败。
</x-alert>

<x-alert type="warning" class="mb-4">
    警告信息
</x-alert>

五、组件属性 #

5.1 组件数据 #

php
// 组件类
class UserCard extends Component
{
    public $user;
    public $showEmail;

    public function __construct($user, $showEmail = false)
    {
        $this->user = $user;
        $this->showEmail = $showEmail;
    }

    public function render()
    {
        return view('components.user-card');
    }
}
blade
<!-- 组件视图 -->
<div class="user-card">
    <h3>{{ $user->name }}</h3>
    @if($showEmail)
        <p>{{ $user->email }}</p>
    @endif
</div>

<!-- 使用 -->
<x-user-card :user="$user" :show-email="true" />

5.2 属性转kebab-case #

blade
<!-- PHP属性名: showEmail -->
<!-- HTML属性名: show-email -->
<x-user-card :show-email="true" />

5.3 $attributes变量 #

blade
<!-- 组件视图 -->
<div {{ $attributes }}>
    内容
</div>

<!-- 使用 -->
<x-alert class="mb-4" id="my-alert">
    消息
</x-alert>

<!-- 输出 -->
<div class="mb-4" id="my-alert">
    消息
</div>

5.4 合并属性 #

blade
<div {{ $attributes->merge(['class' => 'alert']) }}>
    内容
</div>

<!-- 使用 -->
<x-alert class="mb-4" />

<!-- 输出 -->
<div class="alert mb-4">
    内容
</div>

5.5 属性过滤 #

blade
<!-- 只保留特定属性 -->
<button {{ $attributes->only(['class', 'disabled']) }}>
    按钮
</button>

<!-- 排除特定属性 -->
<div {{ $attributes->except(['class']) }}>
    内容
</div>

六、插槽 #

6.1 默认插槽 #

blade
<!-- 组件视图 -->
<div class="card">
    {{ $slot }}
</div>

<!-- 使用 -->
<x-card>
    卡片内容
</x-card>

6.2 具名插槽 #

blade
<!-- 组件视图 -->
<div class="card">
    <div class="card-header">
        {{ $title }}
    </div>
    <div class="card-body">
        {{ $slot }}
    </div>
    <div class="card-footer">
        {{ $footer }}
    </div>
</div>

<!-- 使用 -->
<x-card>
    <x-slot:title>
        卡片标题
    </x-slot:title>
    
    卡片内容
    
    <x-slot:footer>
        卡片底部
    </x-slot:footer>
</x-card>

6.3 插槽属性 #

blade
<!-- 组件视图 -->
<div class="alert alert-{{ $type }}">
    {{ $slot }}
</div>

@props(['type' => 'info'])

<!-- 使用 -->
<x-alert type="success">
    操作成功
</x-alert>

6.4 插槽变量 #

blade
<!-- 组件视图 -->
@foreach($items as $item)
    {{ $slot($item) }}
@endforeach

<!-- 使用 -->
<x-list :items="$users" as="user">
    <li>{{ $user->name }}</li>
</x-list>

七、匿名组件 #

7.1 创建匿名组件 #

匿名组件没有类文件,只有视图文件:

blade
<!-- resources/views/components/button.blade.php -->
@props(['type' => 'button', 'variant' => 'primary'])

<button type="{{ $type }}" {{ $attributes->merge(['class' => 'btn btn-' . $variant]) }}>
    {{ $slot }}
</button>

7.2 使用匿名组件 #

blade
<x-button>默认按钮</x-button>
<x-button variant="danger">删除</x-button>
<x-button type="submit" variant="success">提交</x-button>

7.3 @props指令 #

blade
@props([
    'type' => 'info',
    'dismissible' => false,
])

<div {{ $attributes->merge(['class' => 'alert alert-' . $type]) }}>
    {{ $slot }}
    
    @if($dismissible)
        <button type="button" class="close">×</button>
    @endif
</div>

八、组件方法 #

8.1 在组件中定义方法 #

php
class Alert extends Component
{
    public $type;

    public function __construct($type = 'info')
    {
        $this->type = $type;
    }

    public function classes()
    {
        return 'alert alert-' . $this->type;
    }

    public function render()
    {
        return view('components.alert');
    }
}
blade
<!-- 组件视图 -->
<div class="{{ $this->classes() }}">
    {{ $slot }}
</div>

8.2 访问组件方法 #

blade
<div class="{{ $this->classes() }}">
    {{ $slot }}
</div>

九、内联组件视图 #

9.1 内联渲染 #

php
class Alert extends Component
{
    public $type;

    public function render()
    {
        return <<<'blade'
            <div class="alert alert-{{ $type }}">
                {{ $slot }}
            </div>
        blade;
    }
}

十、组件注册 #

10.1 自动发现 #

组件默认在 app/View/Componentsresources/views/components 中自动发现。

10.2 手动注册 #

php
// app/Providers/AppServiceProvider.php

public function boot()
{
    Blade::component('alert', Alert::class);
}
blade
<x-alert />

10.3 组件别名 #

php
Blade::component(Alert::class, 'alert-message');
blade
<x-alert-message />

十一、组件包 #

11.1 加载组件视图 #

php
// 服务提供者中
public function boot()
{
    $this->loadViewComponentsAs('ui', [
        'button' => ButtonComponent::class,
        'card' => CardComponent::class,
    ]);
}
blade
<x-ui-button>按钮</x-ui-button>
<x-ui-card>卡片</x-ui-card>

十二、实战示例 #

12.1 表单输入组件 #

blade
<!-- resources/views/components/input.blade.php -->
@props(['label', 'type' => 'text', 'error'])

<div class="form-group">
    <label for="{{ $id ?? $name }}">{{ $label }}</label>
    <input 
        type="{{ $type }}" 
        name="{{ $name }}" 
        id="{{ $id ?? $name }}"
        value="{{ $value ?? old($name) }}"
        {{ $attributes->merge(['class' => 'form-control' . ($error ? ' is-invalid' : '')]) }}
    >
    @if($error)
        <div class="invalid-feedback">{{ $error }}</div>
    @endif
</div>
blade
<!-- 使用 -->
<x-input 
    name="email" 
    label="邮箱地址" 
    type="email" 
    :error="$errors->first('email')"
/>

12.2 模态框组件 #

blade
<!-- resources/views/components/modal.blade.php -->
@props(['id', 'title', 'size' => 'md'])

<div class="modal fade" id="{{ $id }}" tabindex="-1">
    <div class="modal-dialog modal-{{ $size }}">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">{{ $title }}</h5>
                <button type="button" class="close" data-dismiss="modal">
                    <span>&times;</span>
                </button>
            </div>
            <div class="modal-body">
                {{ $slot }}
            </div>
            <div class="modal-footer">
                {{ $footer ?? '' }}
            </div>
        </div>
    </div>
</div>
blade
<!-- 使用 -->
<x-modal id="confirmModal" title="确认删除">
    <p>确定要删除这条记录吗?</p>
    
    <x-slot:footer>
        <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
        <button type="button" class="btn btn-danger">删除</button>
    </x-slot:footer>
</x-modal>

十三、总结 #

13.1 核心要点 #

要点 说明
@extends 继承布局
@yield 定义占位区
@section 定义/填充区块
@parent 保留父模板内容
组件 <x-component-name>
插槽 $slot, 具名插槽

13.2 下一步 #

掌握了模板继承与组件后,让我们继续学习 Blade指令,了解更多Blade高级指令!

最后更新:2026-03-28