Laravel 11 Blade 模板中集成 Vue 3 组件的终极指南
2025-03-19 09:56:09
Laravel 11 中 Blade 模板使用 Vue 3 组件
遇到在 Blade 文件里直接用 Vue 3 组件的需求? 你不是一个人! 我也遇到过类似情况,并且摸索出了一些解决方案。 这篇博客,咱就来好好聊聊如何在 Laravel 11 + Inertia.js v2 + Vue 3 的环境下实现这个目标。
问题:Blade 与 Vue 的“隔阂”
问题的根源在于 Blade 是服务器端模板引擎,而 Vue 3 组件是客户端渲染的。直接在 Blade 里写 Vue 组件,浏览器不认识,Inertia.js 也无能为力。 Blade 负责生成 HTML 骨架,Vue 组件则是在这个骨架上进行动态渲染和交互。
解决方案:打破“隔阂”
核心思路就是:先让 Blade 生成一个“占位符” (比如一个空的 <div>
),然后用 Vue 接管这个占位符,渲染 Vue 组件。 下面是几种具体实现方法:
方案一:利用 Inertia.js 的 props
这是我最推荐的方式,充分利用 Inertia.js 作为桥梁的能力。
-
Blade 文件(传递数据):
@inertia('MyVuePage', ['myComponentData' => $data])
这里,
MyVuePage
是一个 Vue 页面组件 (不是你要用的那个小组件)。myComponentData
是一个键,用于传递数据给 Vue 页面。$data
是你要传递给 Vue 组件的数据,可以是一个数组、对象等。 -
Vue 页面组件 (
MyVuePage.vue
,接收并传递数据):<template> <MyComponent :data="myComponentData" /> </template> <script setup> import MyComponent from './MyComponent.vue'; // 引入你的组件 import { defineProps } from 'vue'; defineProps({ myComponentData: { type: Object, // 或者 Array,根据你的数据类型 required: true, }, }); </script>
在这个 Vue 页面组件中,我们通过
props
接收来自 Blade 的数据 (myComponentData
),然后将这个数据传递给你的实际组件 (MyComponent
)。 -
你的 Vue 组件 (
MyComponent.vue
,接收数据并渲染):<template> <div> <!-- 组件内容,使用 data 数据 --> <p>{{ data.message }}</p> </div> </template> <script setup> import { defineProps } from 'vue'; defineProps({ data: { type: Object, required: true } }); </script>
这个组件也用props来接受
data
数据.
原理: Inertia.js 负责在服务器端 (Blade) 和客户端 (Vue) 之间传递数据。 Blade 通过 @inertia
指令将数据作为 props
传递给 Vue 页面组件,Vue 页面组件再将数据传递给你需要的 Vue 组件。
安全建议: 确保传递给 Vue 组件的数据经过适当的验证和清理,防止 XSS 攻击等安全问题。 如果 $data
包含用户输入的内容,一定要进行转义或使用安全的输出方式。
方案二:使用 resolveComponent
(仅限少量组件)
如果你只需要在 Blade 中使用 极少数 几个 Vue 组件,并且不需要传递复杂数据,可以考虑使用 resolveComponent
。
-
Blade 文件:
<div id="my-component"></div> @vite('resources/js/app.js') <script> Inertia.resolveComponent = (name) => { if(name === 'MyComponent'){ const component = require('./components/MyComponent.vue').default; return { setup(){ return ()=> h(component) } } } } </script>
先创建一个空的
<div>
作为占位符,然后在页面内引入app.js之后用js处理该<div>
-
Vue 组件 (
components/MyComponent.vue
):和上面方案的写法相同. 不需要特别处理.
原理 利用
resolveComponent
来加载一个空的vue component 覆盖blade div的内容
进阶用法:
你可以创建一个包含vue组件的div, 将props的值直接写到内联属性里:
<div id="app" data-page="{{ json_encode($page) }}"></div>
@vite('resources/js/app.js')
然后利用inertiajs 的共享数据功能(参考HandleInertiaRequests 中间件) 和 createInertiaApp方法直接使用 app.js
创建整个vue 项目, 通过 $page
来访问所有的prop和vue页面. 如此就可以在你的laravel应用中逐步采用vue技术, 将原本的blade文件替换为vue, 无需完全重写.
方案三:AJAX 动态加载 (适用于需要局部更新的情况)
如果你的 Vue 组件需要在页面加载后,根据用户操作或其他事件动态加载,那么 AJAX 是一个好选择。
-
Blade 文件:
<div id="my-component"></div> <button onclick="loadComponent()">加载组件</button> @vite('resources/js/my-component-loader.js')
创建一个空的
<div>
,以及一个触发加载的按钮 (或者其他事件)。 -
resources/js/my-component-loader.js
(使用 Fetch API):import { createApp, h } from 'vue'; import MyComponent from './components/MyComponent.vue'; window.loadComponent = () => { fetch('/get-component-data') // 请求数据的 URL .then(response => response.json()) .then(data => { const app = createApp({ render: () => h(MyComponent, { data: data }), }); app.mount('#my-component'); }); };
使用
fetch
(或其他 AJAX 库) 从服务器获取数据,然后在获取到数据后,创建一个 Vue 应用,挂载到占位符元素上。 -
Laravel 路由 (
routes/web.php
):Route::get('/get-component-data', function () { $data = ['message' => 'Hello from the server!']; // 示例数据 return response()->json($data); });
创建一个路由,用于返回 Vue 组件所需的数据。
-
你的 Vue 组件 (
MyComponent.vue
) : 保持原样.
原理: Blade 先渲染一个空的占位符和触发器,JavaScript 代码在触发时发送 AJAX 请求到服务器获取数据,然后使用 Vue 动态地渲染组件并填充到占位符中。
安全建议: 与方案一同理。 此外,确保对 AJAX 请求进行身份验证和授权,避免未授权访问。 做好请求出错处理,避免长时间白屏.
方案四:使用自定义 Blade 指令(稍微复杂一点)
如果你想在 Blade 中使用更像 Vue 组件的语法,可以自定义 Blade 指令。
-
创建 BladeServiceProvider (
app/Providers/BladeServiceProvider.php
) (如果没有,就创建一个):<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class BladeServiceProvider extends ServiceProvider { public function boot() { Blade::directive('vue', function ($expression) { return "<?php \$vue_data = $expression; ?>"; }); Blade::directive('endvue', function () { return "<div id='vue-component-".uniqid()."' data-props='<?php echo json_encode(\$vue_data); ?>'></div> <script> document.addEventListener('DOMContentLoaded',function (){ const element=this.currentScript.previousElementSibling import('./components/'.concat(element.dataset.component).concat('.vue')).then(m=> { const app = Vue.createApp({ render:()=>Vue.h(m.default, JSON.parse(element.dataset.props) ) }); app.mount(element); }) }) </script>"; }); } }
这里我们定义了两个指令:
@vue
和@endvue
。@vue
用于传递数据给 Vue 组件。 -
在
config/app.php
的providers
数组中注册这个 ServiceProvider:App\Providers\BladeServiceProvider::class,
-
Blade 文件:
@vue(['component'=>'MyComponent','data' => $myData]) @endvue
这样看起来就有点像 Vue 组件的用法了。
注意这里面假设MyComponent.vue 是放置在resource/js/components
里, 如果你需要支持更多的vue component, 可以考虑传递更多参数.
注意这里的自定义标签的id利用了uniqid
来避免冲突. -
Vue 组件 (
MyComponent.vue
) : 与前面例子保持相同. -
确保执行过
npm run build
.
原理: 自定义 Blade 指令允许你在 Blade 模板中使用更接近 Vue 组件的语法。 通过把数据保存在 data-props
属性里然后利用 js读取该属性来为组件设置参数.
利用DOM事件 DOMContentLoaded
确保组件创建在DOM载入后.
利用js 内联代码在每个组件建立以后分别执行Vue 初始化过程来避免命名冲突.
总结:
选择哪种方案,取决于你的具体需求:
- 少量、简单组件: 方案二 (利用inertia resolve)
- 需要传递数据、或与 Inertia.js 紧密集成: 方案一 (推荐)
- 动态加载、局部更新: 方案三
- 追求 Blade 中类似 Vue 的语法: 方案四.
希望这几种方案可以解决在Blade使用Vue Component时的问题。