Laravel + HTML = ❤️
增强 Chirper:从 Blade 到现代 Web 应用的旅程
本文将探讨如何将 HTML 与 Laravel 结合使用,以实现Laravel Bootcamp Chirper Blade 项目的现代化。作为入门,您可以访问chirper.tlaurentiu.net体验一下在线实现,以便更好地了解我们将要深入探讨的内容。
如果您不想注册,可以使用我的账号(turculaurentiu91@gmail.com | 密码)访问。我们的目标是通过集成HTML
将这个项目转换为一个现代化的 Web 应用程序。
在此过程中,我们将深入研究高级技术,以引入诸如主动搜索、无限滚动以及当其他用户创建新元素时页面的实时更新等功能。
为什么选择HTMX?
因为我太喜欢它了!自从我访问了他们的网站,了解了它能给网页开发者的生活带来的便利之后,我就一直在想,我们用 React(或其他框架🙂)在网页上做的几乎所有事情,都可以用纯 HTML 加上 HTML 增强功能轻松实现。
- 是的,您可以使用 HTTP API 和 React(或其他框架🙂)来实现同样的功能。
- 是的,您可以使用 Inertia.js 实现同样的效果。
- 是的,使用 Livewire 也可以实现同样的效果。
但本教程主要介绍HTMX及其易用性。您可以把它看作是工具箱中的另一个工具,可以用来替代或配合上述技术使用。
本文并非探讨为何要使用它,而是介绍如何使用它。但要点在于:它极其简单,而且能让你的应用程序保持简洁。而简洁就是好!
如果你想了解更多原因,可以看看这篇文章。
入门
为了能够跟随本教程进行操作,您需要先使用 Blade 模板完成 Laravel Bootcamp 的 Chirper 应用开发。如果您已经熟悉 Laravel 和 Blade,并且不想学习该教程,您可以从这里克隆我的解决方案版本(该版本复刻了 Bootcamp 中的所有代码):GitHub 仓库。请务必检出the-beginning分支,因为该main分支包含最终解决方案。
下一步是安装 HTMX。我们可以采用两种方法:
- 通过 npm 安装,并将其与 Vite(一种有助于加快开发的构建工具)打包。
- 在模板的头部通过 CDN 加载脚本。
通常情况下,第一种方法更可取,因为它允许你的基础设施提供 HTTPS 服务,从而让你能够利用 HTTP/2 多路复用流。但是,为了简化本教程,我们将直接在模板中添加 HTTPS 服务。
<!-- resources/views/layouts/app.blade.php -->
<script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
</head>
所以,我们只是从 unpkg 文件中添加了 head 部分的脚本。就这么简单,现在我们可以使用它了。
增强导航功能
如果您打开应用程序、登录,并在“仪表盘”和“Chirps”选项卡之间切换,您会注意到每次切换都会重新加载整个页面。而像 React 这样的框架则能实现无缝导航,无需重新加载页面,这正是它们的优势所在。现在,让我们看看如何利用 HTML 的hx-boost属性来实现类似的无缝导航:
那么让我们看看如何利用 HTML 的hx-boost属性来实现这一点:
<!-- resources/views/layouts/navigation.blade.php -->
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex" hx-boost="true">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
<x-nav-link :href="route('chirps.index')" :active="request()->routeIs('chirps.index')">
{{ __('Chirps') }}
</x-nav-link>
</div>
我们已将元素添加hx-boost="true"到导航链接的父级 div 元素中。HTML 在后台执行的操作是:
- 它将
onClick为链接安装一个事件。 - 它将阻止浏览器的默认导航。
- 它将向属性中指定的 URL 发送 AJAX 请求
href。 - 它会获取HTML文档。
- 它会将
<body>文档的开头替换为请求中收到的内容。 - 它将更新导航 URL,使其
href与点击的锚点的属性相匹配。
现在,当您点击导航链接时,整个页面将不再重新加载,这体现了 HTML 的强大功能和简洁性。
HTML 属性是可继承的,这意味着您可以从祖先元素继承属性。请注意,我们已将属性添加hx-boost到导航链接的父级 div 元素中。实际上,我们可以将该属性移动到整个页面的包装 div 元素上。
因此,我们将其从应用程序布局中移除resources/views/layouts/navigation.blade.php并添加到应用程序布局中。
<!-- resources/views/layouts/app.blade.php -->
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100" hx-boost="true">
<!-- rest of the template -->
因此,我们为所有已登录用户添加了这项功能。现在,无论用户执行什么操作——创建消息、编辑、删除消息或与任何其他表单交互——都不会触发整个页面重新加载。
作业:给访客用户增加提升,使身份验证表单具有相同的提升。
React 完胜,我们的任务完成了。<script>我们只用了一个标签和一个 HTML 属性,就替换掉了 react、react-router、redux、Axios 以及其他一些你可能仅仅因为可以安装就去安装的 js 依赖项。
CSS 过渡
您可能注意到导航链接有 CSS 过渡属性,但它只在鼠标悬停时显示下划线。点击时,下划线会突然出现,这可能会让人感觉很突兀。让我们来优化一下!
<!-- resources/views/layouts/navigation.blade.php -->
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link id="dashboard-link" :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
<x-nav-link id="chirps-link" :href="route('chirps.index')" :active="request()->routeIs('chirps.index')">
{{ __('Chirps') }}
</x-nav-link>
</div>
<!-- rest of the template -->
是的,我们只是在链接上添加了一些 ID。
要将 CSS 过渡效果应用于替换后的内容,必须确保其 ID 在所有请求中保持一致。HTML 底层会执行以下操作:
- 交换内容,
- 按ID匹配元素,
- 将现有属性(在本例中为 CSS 类)应用到新元素上,
- 短暂暂停几毫秒(20)后,将新属性应用于这些元素。
此序列可使 CSS 过渡效果平滑运行,并在活动导航链接更改时为用户提供视觉提示。现在,当您点击导航链接时,下划线将平滑过渡,而不是突然出现,从而以更精致、更专业的外观提升用户体验。
通过这一简单的调整,我们改进了用户在浏览应用程序时获得的视觉反馈,展现了 HTMX 可以为 Laravel 项目带来的用户友好型增强功能的另一个方面。
有关 HTML 如何处理 CSS 过渡的更多详细信息,请参阅HTML 文档中关于 CSS 过渡的页面。
增强编辑操作
想象一下,你的产品负责人来找你,说他不喜欢编辑信息流时跳转到另一个页面。他希望点击编辑按钮后,信息流直接变成一个表单。保存后,它再变回显示更新内容的普通信息流。让我们深入探讨一下如何实现这一点。
编辑按钮
目标是在点击编辑按钮后将提示音转换为表单。为此,我们将创建一个 Blade 组件,用于在编辑模式下渲染提示音。
<!-- resources/views/chirps/edit.blade.php -->
@props(['chirp'])
<div hx-target="this" hx-swap="outerHTML">
<form
class="p-6 flex flex-col space-x-2"
hx-patch="{{ route('chirps.update', $chirp) }}"
>
@csrf
<textarea
name="message"
class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
>
{{ old('message', $chirp->message) }}
</textarea>
<x-input-error :messages="$errors->get('message')" class="mt-2" />
<div class="mt-4 space-x-2">
<x-primary-button>{{ __('Save') }}</x-primary-button>
<a class="cursor-pointer"
hx-get="{{ route('chirps.show', $chirp) }}"
>
{{ __('Cancel') }}
</a>
</div>
</form>
</div>
这里,我们创建了一个新的 Blade 组件,用于内联渲染 Chirp 表单。包装 div 上的` hx-targetand`属性指示 HTML 定位到该 div(自身),并利用 HTML 的属性继承功能,将其 `<response>` 替换为该 div 内所有请求的响应内容。hx-swapouterHTML
在<form>元素和取消链接上,我们分别指定了请求方法为PATCH和GET。
更新控制器
现在,让update我们修改控制器中的方法,以便在收到 HTML 请求时响应部分视图,从而在 JavaScript 被禁用时保持浏览器的正常功能,实现渐进增强型网站。
// app/Http/Controllers/ChirpController.php
public function update(Request $request, Chirp $chirp): RedirectResponse|Response
{
$this->authorize('update', $chirp);
$validated = $request->validate([
'message' => 'required|string|max:255',
]);
$chirp->update($validated);
if($request->header('HX-Request')) {
return response()->view('components.chirps.single', [
'chirp' => $chirp,
]);
}
return redirect(route('chirps.index'));
}
单啁啾分量
现在,让我们来讨论一下我们为了可重用性而提取出来的单个啁啾组件。
<!-- resources/views/components/chirps/single.blade.php -->
@props(['chirp'])
<div class="p-6 flex space-x-2 chirp">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 text-gray-600 -scale-x-100"
fill="none" viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<div class="flex-1">
<div class="flex justify-between items-center">
<div>
<span class="text-gray-800">{{ $chirp->user->name }}</span>
<small class="ml-2 text-sm text-gray-600">
{{ $chirp->created_at->format('j M Y, g:i a') }}
</small>
@unless ($chirp->created_at->eq($chirp->updated_at))
<small class="text-sm text-gray-600">
· {{ __('edited') }}
</small>
@endunless
</div>
@if ($chirp->user->is(auth()->user()))
<x-dropdown>
<x-slot name="trigger">
<button>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor">
<path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />
</svg>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link
:href="route('chirps.edit', $chirp)"
hx-get="{{ route('chirps.edit', $chirp) }}"
hx-target="closest .chirp"
hx-swap="outerHTML"
>
{{ __('Edit') }}
</x-dropdown-link>
<form
method="POST"
action="{{ route('chirps.destroy', $chirp) }}">
@csrf
@method('delete')
<x-dropdown-link
:href="route('chirps.destroy', $chirp)" onclick="event.preventDefault(); this.closest('form').submit();">
{{ __('Delete') }}
</x-dropdown-link>
</form>
</x-slot>
</x-dropdown>
@endif
</div>
<p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>
</div>
</div>
我们已将 Chirp 渲染代码移至可复用的 Blade 组件。编辑按钮上的 HTML 属性确保向编辑端点发出 GET 请求,并将最近的父级 div 元素(带有特定类名)替换chirp为响应标记。
编辑 Chirp 部分/组件
确保发送到编辑 chirp 端点的 HTML 请求仅接收编辑 chirp 组件,而其他请求接收整个页面,符合“渐进增强”模式。
// app/Http/Controllers/ChirpController.php
public function edit(Request $request, Chirp $chirp): Response
{
$this->authorize('update', $chirp);
if($request->header('HX-Request')) {
return response()->view('components.chirps.edit', [
'chirp' => $chirp,
]);
}
return response()->view('chirps.edit', [
'chirp' => $chirp,
]);
}
现在,更新索引布局以使用单个 chirp 组件并实现view单个 chirp 的端点。
<!-- resources/views/chirps/index.blade.php -->
<div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
@foreach ($chirps as $chirp)
<x-chirps.single :chirp="$chirp" />
@endforeach
</div>
public function show(Chirp $chirp): Response
{
return response()->view('components.chirps.single', [
'chirp' => $chirp,
]);
}
别忘了将新端点添加到routes.php。
审查
让我们回顾一下流程:
- 点击编辑按钮会触发一个 GET 请求
chirps/{chirp}/edit,将带有 class 的父 div 替换chirp为来自部分模板的响应标记components/chirps/edit。 - 点击表单上的取消链接会触发一个 GET 请求
chirps/{chirp},将包装 div 元素替换为响应的内容,即单个 chirp 元素的部分标记。 - 提交编辑表单会触发向
chirps/{chirp}端点发送 PATCH 请求,更新 chirp 并将包装 div 元素替换为单个 chirp(即已编辑的 chirp)的标记。
通过添加HX-Request头部信息,即使 JavaScript 被禁用,我们也能保持浏览器的正常功能,这体现了渐进增强的原则。我们的应用程序依然采用服务器端渲染,速度快、操作简单,并且用户体验显著提升。
PS:请注意,目前验证功能尚未生效;我们将在本教程的后续部分解决这个问题。
通过这些步骤,我们已将 HTMX 无缝集成到 Laravel 应用程序中,以增强编辑操作,从而提供更直观、更具交互性的用户体验,而无需复杂的前端框架。
增强创建操作。
通过进行两项关键更改,可以增强创建操作:
- 修改存储操作,使其在 HTMX 请求期间返回单个 chirp。
- 在 chirps 列表的开头注入单个 chirp 标记。
让我们深入了解这些步骤:
更新商店操作
更新该store方法,使其在ChirpController检测到 HTML 请求时返回单个 chirp 的部分视图:
// app/Http/Controllers/ChirpController.php
public function store(Request $request): RedirectResponse|Response
{
$validated = $request->validate([
'message' => 'required|string|max:255',
]);
$chirp = $request->user()->chirps()->create($validated);
if($request->header('HX-Request')) {
return response()->view('components.chirps.single', [
'chirp' => $chirp,
]);
}
return redirect()->route('chirps.index');
}
修改索引模板
在chirps.index模板中,通过添加 HTML 属性更新表单元素,指示其将响应插入到消息列表的顶部(afterbegin)。此外,为消息列表的 div 添加一个 ID 以便进行定向:
<form
method="POST"
action="{{ route('chirps.store') }}"
hx-post="{{ route('chirps.store') }}"
hx-target="#chirps"
hx-swap="afterbegin" >
<!-- rest of the template -->
<div id="chirps" class="mt-6 bg-white shadow-sm rounded-lg divide-y">
处理表单重置
您可能会注意到,即使请求成功,文本区域的内容仍然保持不变。为了提升用户体验,让我们使用以下hx-on属性在表单提交成功后重置表单:
<form
method="POST"
action="{{ route('chirps.store') }}"
hx-post="{{ route('chirps.store') }}"
hx-target="#chirps"
hx-swap="afterbegin"
hx-on="htmx:afterRequest: if(event.detail.successful) this.reset();"
>
这里,我们使用hx-on属性将脚本绑定到htmx:afterRequest事件,HTML 会在每次请求后触发该事件。我们使用检查请求是否成功event.detail.successful,如果成功,则使用重置表单this.reset()。
好了,就是这样!我们已经大幅改进了创建方法。
注意:验证功能目前尚未正常工作,但我们将在教程的后续部分解决这个问题。
这些步骤展示了我们如何无缝集成 HTML 来改进 Laravel 应用程序中的创建操作,从而在不增加前端复杂性的情况下提供更好的用户体验。
处理删除操作
删除操作相对简单。对于成功的 HTML 删除请求,我们可以返回一个空字符串,指示 HTML 从 DOM 中移除该项。对于非 HTML 请求,行为保持不变。
更新销毁方法
修改该destroy方法,使其在ChirpController收到 HTML 请求时返回空字符串,否则重定向到该chirps.index路由。
public function destroy(Request $request, Chirp $chirp): RedirectResponse|string
{
$this->authorize('delete', $chirp);
$chirp->delete();
if($request->header('HX-Request')) {
return '';
}
return redirect(route('chirps.index'));
}
修改下拉链接组件
Laravel Bootcamp Chirper 目前的实现中有一个<x-dropdown-link>组件始终渲染一个链接。为了确保即使在 JavaScript 被禁用的情况下也能正常工作,请将其修改为渲染一个按钮,如下所示:
<!-- resources/views/components/dropdown-link.blade.php -->
@props(['component' => 'link'])
@if ($component === 'link')
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
@else
<button {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</button>
@endif
这里我厚颜无耻地复制了类,为了遵循 DRY 原则,没有使用变量,而是直接照搬了代码。
调整单啁啾分量
现在,更新单个 chirp 组件以使用修改后的组件<x-dropdown-link>,并调整表单以执行删除请求,在请求完成后从 DOM 中删除 chirp。
<!-- resources/views/components/chirps/single.blade.php -->
<form method="POST"
action="{{ route('chirps.destroy', $chirp) }}"
hx-delete="{{route('chirps.destroy', $chirp)}}"
hx-target="closest .chirp"
hx-swap="delete"
>
@csrf
@method('delete')
<x-dropdown-link
:component="'button'"
type="submit">
{{ __('Delete') }}
</x-dropdown-link>
</form>
现在,提交按钮被渲染成下拉链接,表单也进行了调整,以发出删除请求,并在请求完成后从 DOM 中删除 chirp。
虽然需要注意的是,下拉链接现在使用了 Alpine.js,如果没有 JavaScript 可能无法正常工作,但这超出了本教程的范围。本教程的重点在于展示如何通过 HTML 集成无缝处理删除操作。
实现无限滚动
我们首先使用 Laravel 的工厂和种子功能生成额外的 chirp:
php artisan make:factory ChirpFactory --model Chirp
<?php
namespace Database\Factories;
use App\Models\Chirp;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Chirp>
*/
class ChirpFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$created_at = $this->faker->dateTimeBetween('-2 months');
return [
'message' => $this->faker->sentence,
'created_at' => /* somewhere in the past two months */ $created_at,
'updated_at' => $this->faker->boolean() ? $this->faker->dateTimeBetween($created_at) : null,
];
}
}
// DatabaseSeeder.php
public function run(): void
{
User::factory(20)->hasChirps(2000)->create();
}
现在,运行种子命令,然后去喝杯咖啡吧,这需要一段时间,我们要生成 20 个用户,每个用户发送 2000 个 chirp,所以这需要一段时间。
php aritsan db:seed
实现基本分页
为防止页面过载,请在以下位置实现基本分页ChirpController:
// app/Http/Controllers/ChirpController.php
public function index(): Response
{
$chirps = Chirp::with('user')->latest()->paginate(25);
return \response()->view('chirps.index', [
'chirps' => $chirps
]);
}
在以下位置显示分页链接chirps.index:
<!-- resources/views/chirps/index.blade.php -->
<noscript>
<div class="max-w-5xl mx-auto p-4 sm:p-6 lg:p-8">
{{ $chirps->links() }}
</div>
</noscript>
添加无限滚动
利用 HTML 实现无限滚动,同时保留分页链接作为备用方案:
<!-- resources/views/chirps/index.blade.php -->
<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
<!-- rest of the template -->
@foreach ($chirps as $chirp)
<x-chirps.single :chirp="$chirp" />
@endforeach
@if($chirps->nextPageUrl())
<div
hx-get="{{ $chirps->nextPageUrl() }}"
hx-select="#chirps>div"
hx-swap="outerHTML"
hx-trigger="intersect"
>
Loading more...
</div>
@endif
</div>
</div>
<noscript>
<div class="max-w-5xl mx-auto p-4 sm:p-6 lg:p-8">
{{ $chirps->links() }}
</div>
</noscript>
请确保x-cloak元素初始状态为隐藏:
<!-- resources/views/layouts/app.blade.php -->
<style>
[x-cloak] { display: none !important; }
</style>
之所以称之为关键元素,是因为它会在 CSS 文件下载之前加载,所以才要把它放在头部而不是 CSS 文件中。
无限滚动代码片段中使用的 HTML 属性为浏览器提供了必要的指令,使其能够处理用户向下滚动页面时加载的额外信息。让我们来详细了解一下这些属性:
hx-get="{{ $chirps->nextPageUrl() }}"此属性指示 HTML 向指定的 URL 发出 GET 请求,在本例中,该 URL 是下一页鸣叫内容的 URL。当用户向下滚动并到达“正在加载更多...”div 时,将触发此操作。hx-select="#chirps>div"此属性指定当前页面应使用 GET 请求返回内容的哪一部分。在本例中,它选择divid 为 `<id>` 的元素的所有直接子元素#chirps。这实际上会选择下一页结果中返回的所有 chirps。hx-swap="outerHTML"此属性指定如何使用 GET 请求中选定的元素更新 DOM。该值outerHTML指示 HTML 将“正在加载更多...”div 完全替换为从服务器获取的新内容。这样,新加载的 chirps 将被添加到列表中,“正在加载更多...”div 将被这些新 chirps 替换。hx-trigger="intersect"此属性指定何时触发 GET 请求。该值intersect指示 HTML 在“正在加载更多...”div 元素进入视图(即与视口重叠时)时触发请求。这正是实现无限滚动效果的关键所在,因为每次用户向下滚动并到达当前列表末尾时,都会加载新的提示信息。x-cloak这是一个 Alpine.js 指令,而不是 HTML 属性。它用于初始隐藏某些元素。启用 JavaScript 后,Alpine.js 会移除该x-cloak属性,使元素可见。
通过利用这些 HTML 属性和 Alpine.js 指令,代码可以实现无缝的无限滚动体验,同时还为禁用 JavaScript 的用户提供备用分页系统。这样,应用程序在不同的用户体验场景下都能保持良好的可访问性和易用性。是不是很棒?!
结论
在本教程中,我们逐步使用 HTML 为一个基本的 Laravel 聊天应用添加了更多功能。我们从简单的表单提交改进入手,逐步实现了在线编辑和删除聊天内容,最终实现了无限滚动,从而高效地加载和显示大量聊天内容。每一次改进都旨在提供更具交互性和用户友好的体验,同时在不使用 JavaScript 的情况下保持功能,遵循渐进增强的原则。
HTML 的简洁性和强大功能得以充分展现,使我们能够将代码主要放在服务器端,保持代码的简洁性和可维护性,同时还能提供动态的用户体验。从传统的服务器端渲染交互过渡到更动态的界面,无需完全重写代码或引入复杂的客户端框架。这充分证明了现代技术如何在遵循良好实践的同时简化开发流程。
接下来:深入了解互动功能
在本教程的第二部分中,我们将通过引入更多交互式和实时功能,将我们的应用程序提升到一个新的水平:
- 加载指示器:通过添加加载指示器来提升用户体验
- 主动搜索:我们将探讨如何使用 MySQL 全文索引来实现主动搜索功能,以帮助用户快速找到他们感兴趣的聊天记录。
- 服务器端验证:为了解决一个遗留问题,我们将着手修复服务器端验证,以确保数据完整性并提供用户反馈。
- 实时更新:我们将使用池化方法,实时更新索引页面,添加新的消息,保持内容新鲜有趣,吸引用户。
- 尝试使用服务器端事件:我们将尝试实现服务器端事件,以进一步增强应用程序的实时交互性。
冒险之旅仍在继续,我们将深入探索如何打造更具互动性和实时性的体验,同时又不牺牲 Laravel 应用的简洁性和可维护性。敬请期待,准备好探索更多让您的 Laravel 应用焕发生机的方法!
文章来源:https://dev.to/turculaurentiu91/laravel-htmx--g0n