返回

Laravel 11 Blade 模板中集成 Vue 3 组件的终极指南

javascript

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 作为桥梁的能力。

  1. Blade 文件(传递数据):

    @inertia('MyVuePage', ['myComponentData' => $data])
    

    这里,MyVuePage 是一个 Vue 页面组件 (不是你要用的那个小组件)。myComponentData 是一个键,用于传递数据给 Vue 页面。$data 是你要传递给 Vue 组件的数据,可以是一个数组、对象等。

  2. 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)。

  3. 你的 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

  1. 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>

  2. 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 是一个好选择。

  1. Blade 文件:

    <div id="my-component"></div>
    
    <button onclick="loadComponent()">加载组件</button>
    
    @vite('resources/js/my-component-loader.js')
    

    创建一个空的 <div>,以及一个触发加载的按钮 (或者其他事件)。

  2. 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 应用,挂载到占位符元素上。

  3. Laravel 路由 (routes/web.php):

    Route::get('/get-component-data', function () {
        $data = ['message' => 'Hello from the server!']; // 示例数据
        return response()->json($data);
    });
    

    创建一个路由,用于返回 Vue 组件所需的数据。

  4. 你的 Vue 组件 (MyComponent.vue) : 保持原样.

原理: Blade 先渲染一个空的占位符和触发器,JavaScript 代码在触发时发送 AJAX 请求到服务器获取数据,然后使用 Vue 动态地渲染组件并填充到占位符中。

安全建议: 与方案一同理。 此外,确保对 AJAX 请求进行身份验证和授权,避免未授权访问。 做好请求出错处理,避免长时间白屏.

方案四:使用自定义 Blade 指令(稍微复杂一点)

如果你想在 Blade 中使用更像 Vue 组件的语法,可以自定义 Blade 指令。

  1. 创建 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 组件。

  2. config/app.phpproviders 数组中注册这个 ServiceProvider:

     App\Providers\BladeServiceProvider::class,
    
  3. Blade 文件:

     @vue(['component'=>'MyComponent','data' => $myData])
     @endvue
    

    这样看起来就有点像 Vue 组件的用法了。
    注意这里面假设MyComponent.vue 是放置在 resource/js/components里, 如果你需要支持更多的vue component, 可以考虑传递更多参数.
    注意这里的自定义标签的id利用了 uniqid 来避免冲突.

  4. Vue 组件 (MyComponent.vue) : 与前面例子保持相同.

  5. 确保执行过 npm run build.

原理: 自定义 Blade 指令允许你在 Blade 模板中使用更接近 Vue 组件的语法。 通过把数据保存在 data-props 属性里然后利用 js读取该属性来为组件设置参数.
利用DOM事件 DOMContentLoaded 确保组件创建在DOM载入后.
利用js 内联代码在每个组件建立以后分别执行Vue 初始化过程来避免命名冲突.

总结:

选择哪种方案,取决于你的具体需求:

  • 少量、简单组件: 方案二 (利用inertia resolve)
  • 需要传递数据、或与 Inertia.js 紧密集成: 方案一 (推荐)
  • 动态加载、局部更新: 方案三
  • 追求 Blade 中类似 Vue 的语法: 方案四.

希望这几种方案可以解决在Blade使用Vue Component时的问题。