blade模板复用需分场景:布局继承用于全局结构,@include适合局部静态复用,组件(x-标签)才是动态、可测试、带逻辑的真正复用单元。

Blade 模板复用不是“能用就行”,而是要分清场景选对机制:布局继承用于全局结构,@include 适合局部静态复用,组件(x- 标签)才是动态、可测试、带逻辑的真正复用单元。
什么时候该用 @extends 而不是 @include
布局骨架(如 HTML 结构、导航栏、页脚)必须用 @extends,否则无法保证 CSS/JS 加载顺序、SEO 元信息统一、或全局状态(如用户登录态)在所有页面一致生效。
- 错误做法:把
header.blade.php和footer.blade.php全部用@include拼成页面 —— 这会导致每个页面重复加载相同 JS/CSS,且@stack('scripts')在子视图中无法被父布局收集 - 正确路径:只保留一个
layouts/app.blade.php,里面用@yield('content')和@stack('scripts');所有页面都@extends('layouts.app') - 注意:父布局中
@yield('title', 'My App')的默认值仅在子视图完全没定义@section('title')时才生效;如果子视图写了空@section('title')@endsection,它会覆盖默认值并渲染为空
@include 传参必须加默认值(??)
直接写 @include('partials.chart', ['title' => $pageTitle]) 很危险 —— 如果 $pageTitle 是 null 或未定义,模板会报 Undefined variable 错误,且无法 fallback。
- 安全写法是组件内部用
{{ $title ?? 'Default Chart' }},而不是依赖调用方一定传参 - 更稳妥的是在
@include时就兜底:@include('partials.chart', ['title' => $pageSubtitle ?? 'Overview']) - 不要在
@include中传复杂对象(如整个$user),只传必要字段('user_name' => $user->name),避免组件意外访问不存在的属性
组件(x-)比 @include 多出的关键能力
Blade 组件不是语法糖,它有独立生命周期、类型约束和作用域隔离 —— 这些是 @include 完全不具备的。
- 组件类中的
mount()方法可以做参数校验:if (!in_array($type, ['success', 'warning'])) { $this->type = 'info'; },@include做不到 - 组件支持插槽(
{{ $slot }})和具名插槽({{ $footer }}),能承载结构化内容;@include只能传扁平变量 - 组件属性自动过滤:
<x-button :disabled="$isSubmitting"></x-button>中$isSubmitting会被自动 cast 为布尔值;而@include传进去的还是原始 PHP 类型,容易出错 - 组件可被单元测试(测试
Alert::class的构造函数和render()),@include模板无法单独测
自定义指令别滥用,优先考虑组件或 @include
注册 Blade::directive('currency') 看似简洁,但实际增加了维护成本:它绕过 PHP 类型系统、无法 IDE 跳转、不能被测试、升级 Laravel 时容易因编译器变更而失效。
- 只有当逻辑极度简单、高频、且不依赖上下文状态时才考虑指令,例如
@datetime($post->created_at) - 一旦涉及格式配置(如时区、语言)、条件判断(是否显示)、或需要访问服务(如货币转换器),立刻退回到组件 —— 写一个
<x-currency :amount="$price" :currency="'USD'"></x-currency>更清晰可控 - 所有自定义指令必须返回纯 PHP 输出语句(
<?php echo ... ?>),不能包含 Blade 语法,否则编译失败
最常被忽略的一点:Blade 编译缓存位于 storage/framework/views/,修改组件类或自定义指令后,必须运行 php artisan view:clear 才能看到效果 —— 否则你会以为代码没生效,其实只是旧缓存还在用。











