Livewire4 正式发布!PHP 也可以无需写一行 Javascript 代码就能实现 Vue 的功能

Livewire4 正式发布!PHP 也可以无需写一行 Javascript 代码就能实现 Vue 的功能

Livewire 4 正式发布,这是迄今为止最大的一次版本更新。

这次更新的重点不是增加复杂度,而是更好的默认配置、更少的摩擦、更强大的工具。团队花了几个月时间重新思考 Livewire 组件应该是什么样子。

原文 Livewire4 正式发布!PHP 也可以无需写一行 Javascript 代码就能实现 Vue 的功能

基于视图的组件

Livewire 4 最直观的变化是组件的写法。以前需要在 PHP 类和 Blade 文件之间来回切换,现在可以把所有东西放在一个文件里:

php 复制代码
<?php // resources/views/components/⚡counter.blade.php
 
use Livewire\Component;
 
new class extends Component {
    public $count = 0;
 
    public function increment()
    {
        $this->count++;
    }
};
?>
 
<div>
    <h1>{{ $count }}</h1>
    <button wire:click="increment">+</button>
</div>
 
<style>
    /* Scoped CSS... */
</style>
 
<script>
    /* Component JavaScript... */
</script>

运行 php artisan make:livewire 时默认就是这种格式。文件名里的闪电符号让 Livewire 组件在文件树里一眼就能认出来,不会和普通 Blade 组件混淆。(不喜欢 emoji 的话可以关掉。)

对于大型组件,还有一种多文件格式,把相关文件放在同一个目录下:

text 复制代码
⚡counter/
├── counter.php
├── counter.blade.php
├── counter.css          (可选)
├── counter.js           (可选)
└── counter.test.php     (可选)

--mfc 参数创建多文件组件,随时可以用 php artisan livewire:convert 在两种格式之间转换。

路由

组件引用方式统一了。Livewire 4 引入了 Route::livewire()

php 复制代码
// 之前 (v3) - 仍然支持
Route::get('/posts/create', CreatePost::class);
 
// 现在 (v4)
Route::livewire('/posts/create', 'pages::post.create');

新语法用名称而不是类来引用组件,和应用其他地方渲染组件的方式一致。

命名空间

Livewire 现在对应用结构有了自己的约定。默认提供两个命名空间:pages:: 用于页面组件,layouts:: 用于布局------其他组件和 Blade 组件一起放在 resources/views/components 目录下。

php 复制代码
Route::livewire('/dashboard', 'pages::dashboard');

对于模块化应用,可以注册自定义命名空间。把管理后台组件放在 admin:: 下,计费相关的放在 billing:: 下,按你的架构来组织。

脚本和样式

组件的 JavaScript 和 CSS 现在可以和组件放在一起。直接在模板里加 <script><style> 标签:

html 复制代码
<div>
    <h1 class="title">{{ $count }}</h1>
    <button wire:click="$js.celebrate">+</button>
</div>
 
<style>
.title {
    color: blue;
    font-size: 2rem;
}
</style>
 
<script>
    this.$js.celebrate = () => {
        confetti()
    }
</script>

样式自动限定在组件范围内------你的 .title 类不会影响页面其他部分。需要全局样式的话,加上 global 属性:<style global>

脚本里可以用 this 访问组件上下文------相当于你可能用过的 $wire 的别名。

两者都会作为原生 .js/.css 文件发送到浏览器,自动缓存以获得最佳性能。

Islands

Islands 是 Livewire 4 的重头戏。它可以在组件内创建独立更新的隔离区域:

html 复制代码
<div>
    @island
        <div>
            Revenue: {{ $this->revenue }}
            <button wire:click="$refresh">Refresh</button>
        </div>
    @endisland
 
    <div>
        <!-- 这部分在 island 更新时不会重新渲染 -->
        Other content...
    </div>
</div>

点击"Refresh"时,只有 island 部分重新渲染,其他内容保持不变。以前要实现类似的隔离效果,需要把这部分提取成单独的子组件,还要处理 props 和 events 的传递。

性能提升不只是 DOM 更新层面。当 islands 和计算属性配合使用时,只有该 island 需要的数据才会被获取。如果组件有三个 island,各自引用不同的计算属性,刷新一个 island 只会执行那个 island 的查询。从数据库到渲染 HTML,整个链路的开销都被隔离了。

Islands 支持懒加载(lazy: true)、命名以便跨组件定位(name: 'revenue'),以及追加内容用于无限滚动:

html 复制代码
<button wire:click="loadMore" wire:island.append="feed">
    Load more
</button>

插槽和属性转发

如果你用过 Blade 组件的插槽和属性转发,这里会很熟悉。

插槽让父组件可以向子组件注入内容,同时保持响应式:

html 复制代码
<livewire:card :$post>
    <h2>{{ $post->title }}</h2>
    <button wire:click="delete({{ $post->id }})">Delete</button>
</livewire:card>

插槽内容在父组件的上下文中求值,所以 wire:click="delete" 调用的是父组件的方法。

属性转发可以把 HTML 属性传递下去:

html 复制代码
<livewire:post.show :$post class="mt-4" />
 
<!-- post.show 组件内部 -->
<div {{ $attributes }}>
    ...
</div>

拖拽排序

内置拖拽排序,不需要外部库:

html 复制代码
<ul wire:sort="reorder">
    @foreach ($items as $item)
        <li wire:key="{{ $item->id }}" wire:sort:item="{{ $item->id }}">
            {{ $item->title }}
        </li>
    @endforeach
</ul>
php 复制代码
public function reorder($item, $position)
{
    // $item 是 ID,$position 是新的索引
}

动画效果自动处理。用 wire:sort:handle 添加拖拽手柄,用 wire:sort:ignore 防止交互元素触发拖拽,用 wire:sort:group 在多个列表之间拖拽。

平滑过渡

wire:transition 指令使用浏览器的 View Transitions API 添加硬件加速动画:

html 复制代码
@if ($showAlertMessage)
    <div wire:transition>
        <!-- 消息平滑淡入淡出 -->
    </div>
@endif

对于步骤向导或轮播这种需要方向感的场景,可以指定过渡类型:

php 复制代码
#[Transition(type: 'forward')]
public function next() { $this->step++; }
 
#[Transition(type: 'backward')]
public function previous() { $this->step--; }

然后用 ::view-transition-old()::view-transition-new() 伪元素为每个方向自定义 CSS 动画。

乐观更新

让界面响应更即时。这些指令会立即更新页面,不需要等待服务器响应。

wire:show 用 CSS 切换可见性(不移除 DOM,不发网络请求):

html 复制代码
<div wire:show="showModal">
    <!-- 立即显示/隐藏 -->
</div>

wire:text 立即更新文本内容:

html 复制代码
Likes: <span wire:text="likes"></span>

wire:bind 响应式绑定任意 HTML 属性:

html 复制代码
<input wire:model="message" wire:bind:class="message.length > 240 && 'text-red-500'">

$dirty 跟踪未保存的更改:

html 复制代码
<div wire:show="$dirty">You have unsaved changes</div>
<div wire:show="$dirty('title')">Title modified</div>

加载状态

除了 v3 已有的 wire:loading,Livewire 4 会自动给触发网络请求的元素添加 data-loading 属性。

这样可以直接用 CSS 设置加载状态样式,还能定位兄弟、父级或子元素:

html 复制代码
<button wire:click="save" class="data-loading:opacity-50">
    Save <svg class="not-in-data-loading:hidden">...</svg>
</button>

内联占位符

对于懒加载组件和 islands,@placeholder 指令可以在内容旁边直接定义加载状态:

html 复制代码
@placeholder
    <div class="animate-pulse h-32 bg-gray-200 rounded"></div>
@endplaceholder
 
<div>
    <!-- 实际内容加载到这里 -->
</div>

不需要单独的占位符视图或方法------骨架屏就在组件里面。

JavaScript 工具

需要用 JavaScript 的时候,Livewire 4 也能配合。

wire:ref 给元素命名以便定位:

html 复制代码
<livewire:modal wire:ref="modal" />
php 复制代码
$this->dispatch('close')->to(ref: 'modal');

也可以在组件脚本里访问 refs:

html 复制代码
<input wire:ref="search" type="text" />
 
<script>
    this.$refs.search.addEventListener('keydown', (e) => {
        // 处理键盘事件...
    })
</script>

#[Json] 方法直接返回数据给 JavaScript:

php 复制代码
#[Json]
public function search($query)
{
    return Post::where('title', 'like', "%{$query}%")->get();
}
html 复制代码
<script>
    let results = await this.search('livewire')
    console.log(results)
</script>

$js actions 只在客户端运行:

html 复制代码
<button wire:click="$js.bookmark">Bookmark</button>
 
<script>
    this.$js.bookmark = () => {
        this.bookmarked = !this.bookmarked
        this.save()
    }
</script>

拦截器可以在各个层级钩入请求:

html 复制代码
<script>
    this.intercept('save', ({ onSuccess, onError }) => {
        onSuccess(() => showToast('Saved!'))
        onError(() => showToast('Failed to save', 'error'))
    })
</script>

用全局拦截器处理应用级别的问题,比如会话过期:

javascript 复制代码
Livewire.interceptRequest(({ onError }) => {
    onError(({ response, preventDefault }) => {
        if (response.status === 419) {
            preventDefault()
            if (confirm('Session expired. Refresh?')) {
                window.location.reload()
            }
        }
    })
})

升级

Livewire 4 保持了很好的向后兼容性。现有组件可以继续使用------新的单文件格式是新组件的默认选项,但基于类的组件仍然完全支持。

查看升级指南 →

如果想看实际演示,我在 Laracasts 上录了一个新系列,用真实案例深入讲解每个特性。观看 Livewire 4 系列 →

Livewire 4 现在可用:

bash 复制代码
composer require livewire/livewire:^4.0

Laravel Livewire 中文文档正式发布

相关推荐
追逐时光者9 小时前
一个致力于为 C# 程序员提供更佳的编码体验和效率的 Visual Studio 扩展插件
后端·c#·visual studio
行百里er10 小时前
用 ThreadLocal + Deque 打造一个“线程专属的调用栈” —— Spring Insight 的上下文管理术
java·后端·架构
玄〤11 小时前
黑马点评中 VoucherOrderServiceImpl 实现类中的一人一单实现解析(单机部署)
java·数据库·redis·笔记·后端·mybatis·springboot
J_liaty11 小时前
Spring Boot拦截器与过滤器深度解析
java·spring boot·后端·interceptor·filter
短剑重铸之日11 小时前
《7天学会Redis》Day2 - 深入Redis数据结构与底层实现
数据结构·数据库·redis·后端
码事漫谈11 小时前
从C++到C#的转型完全指南
后端
码事漫谈11 小时前
TCP心跳机制:看不见的“生命线”
后端
lpfasd12312 小时前
Spring Boot 4.0.1 时变更清单
java·spring boot·后端
梦梦代码精13 小时前
《全栈开源智能体:终结企业AI拼图时代》
人工智能·后端·深度学习·小程序·前端框架·开源·语音识别