Skip to content

从 v3 升级到 v4

Livewire v4 引入了多项改进和优化,同时尽可能保持向后兼容性。本指南将帮助你从 Livewire v3 升级到 v4。

平滑的升级路径

大多数应用可以通过最小的更改升级到 v4。破坏性更改主要是配置更新和方法签名更改,这些只影响高级用法。

安装

更新你的 composer.json 以引入 Livewire v4:

bash
composer require livewire/livewire:^4.0

更新后,清除应用程序的缓存:

bash
php artisan optimize:clear

在 GitHub 上查看所有更改

要全面了解 v3 和 v4 之间的所有代码更改,你可以在 GitHub 上查看完整的差异:对比 3.x 与 main →

高影响更改

这些更改最有可能影响你的应用程序,应仔细检查。

配置文件更新

多个配置键已被重命名、重组或有新的默认值。更新你的 config/livewire.php 文件:

查看完整配置文件

作为参考,你可以在 GitHub 上查看完整的 v4 配置文件:livewire.php →

重命名的配置键

布局配置:

php
// 之前 (v3)
'layout' => 'components.layouts.app',

// 之后 (v4)
'component_layout' => 'layouts::app',

布局现在默认使用 layouts:: 命名空间,指向 resources/views/layouts/app.blade.php

占位符配置:

php
// 之前 (v3)
'lazy_placeholder' => 'livewire.placeholder',

// 之后 (v4)
'component_placeholder' => 'livewire.placeholder',

更改的默认值

智能 wire:key 行为:

php
// 现在默认为 true(v3 中是 false)
'smart_wire_keys' => true,

这有助于防止深度嵌套组件上的 wire:key 问题。注意:你仍然需要在循环中手动添加 wire:key——此设置不会消除该要求。

了解更多关于 wire:key →

新配置选项

组件位置:

php
'component_locations' => [
    resource_path('views/components'),
    resource_path('views/livewire'),
],

定义 Livewire 查找单文件和多文件(基于视图的)组件的位置。

组件命名空间:

php
'component_namespaces' => [
    'layouts' => resource_path('views/layouts'),
    'pages' => resource_path('views/pages'),
],

为组织基于视图的组件创建自定义命名空间(例如,<livewire:pages::dashboard />)。

Make 命令默认值:

php
'make_command' => [
    'type' => 'sfc',  // 选项: 'sfc', 'mfc', 或 'class'
    'emoji' => true,   // 是否使用 ⚡ 表情符号前缀
],

配置默认组件格式和表情符号使用。设置 type'class' 以匹配 v3 行为。

CSP 安全模式:

php
'csp_safe' => false,

启用内容安全策略模式以避免 unsafe-eval 违规。启用后,Livewire 使用 Alpine CSP 构建。注意:此模式限制了指令中的复杂 JavaScript 表达式,如 wire:click="addToCart($event.detail.productId)" 或全局引用如 window.location

路由更改

对于全页面组件,推荐的路由方式已更改:

php
// 之前 (v3) - 仍然有效但不推荐
Route::get('/dashboard', Dashboard::class);

// 之后 (v4) - 推荐用于所有组件类型
Route::livewire('/dashboard', Dashboard::class);

// 对于基于视图的组件,你可以使用组件名称
Route::livewire('/dashboard', 'pages::dashboard');

使用 Route::livewire() 现在是首选方法,对于单文件和多文件组件作为全页面组件正常工作是必需的。

了解更多关于路由 →

wire:model 现在默认忽略子元素事件

在 v3 中,wire:model 会响应从子元素冒泡上来的 input/change 事件。当在包含表单输入的容器元素(如模态框或手风琴)上使用 wire:model 时,这会导致意外行为——清除内部的输入会冒泡上来并可能关闭模态框。

在 v4 中,wire:model 现在只监听直接发生在元素本身的事件(相当于 .self 修饰符行为)。

如果你有依赖捕获子元素事件的代码,添加 .bubble 修饰符:

blade
<!-- 之前 (v3) - 默认监听子元素事件 -->
<div wire:model="value">
    <input type="text">
</div>

<!-- 之后 (v4) - 添加 .bubble 恢复旧行为 -->
<div wire:model.deep="value">
    <input type="text">
</div>

大多数应用不需要更改

此更改主要影响在容器元素上使用 wire:model 的非标准用法。标准表单输入绑定(inputs、selects、textareas)不受影响。

使用 wire:navigate:scroll

在 v3 中使用 wire:scrollwire:navigate 请求之间保持可滚动容器的滚动位置时,在 v4 中需要改用 wire:navigate:scroll

blade
@persist('sidebar')
    <div class="overflow-y-scroll" wire:navigate:scroll>
        <!-- ... -->
    </div>
@endpersist

中等影响更改

这些更改可能会影响应用程序的某些部分,具体取决于你使用的功能。

wire:transition 现在使用 View Transitions API

在 v3 中,wire:transition 是 Alpine 的 x-transition 指令的包装器,支持 .opacity.scale.duration.200ms.origin.top 等修饰符。

在 v4 中,wire:transition 改为使用浏览器原生的 View Transitions API。基本用法仍然有效——元素会平滑地淡入淡出——但所有修饰符已被移除。

blade
<!-- 这在 v4 中仍然有效 -->
<div wire:transition>...</div>

<!-- 这些修饰符不再支持 -->
<div wire:transition.opacity>...</div>
<div wire:transition.scale.origin.top>...</div>
<div wire:transition.duration.500ms>...</div>

了解更多关于 wire:transition →

性能改进

Livewire v4 包含对请求处理系统的显著性能改进:

  • 非阻塞轮询wire:poll 不再阻塞其他请求,也不会被其他请求阻塞
  • 并行实时更新wire:model.live 请求现在并行运行,允许更快的输入和更快的结果

这些改进自动发生——你的代码无需更改。

更新钩子合并数组/对象更改

当从前端替换整个数组或对象时(例如,$wire.items = ['new', 'values']),Livewire 现在发送单个合并更新,而不是每个索引的细粒度更新。

之前: 在有 4 个项目的数组上设置 $wire.items = ['a', 'b'] 会多次触发 updatingItems/updatedItems 钩子——每个索引更改一次,加上 __rm__ 删除。

之后: 相同的操作只触发一次钩子,带有完整的新数组值,匹配 v2 行为。

如果你的代码依赖于替换整个数组时触发单个索引钩子,可能需要调整。单项更改(如 wire:model="items.0")仍然按预期触发细粒度钩子。

方法签名更改

如果你正在扩展 Livewire 的核心功能或直接使用这些方法,请注意这些签名更改:

流式传输:

stream() 方法参数顺序已更改:

php
// 之前 (v3)
$this->stream(to: '#container', content: 'Hello', replace: true);

// 之后 (v4)
$this->stream(content: 'Hello', replace: true, el: '#container');

如果你使用命名参数(如上所示),请注意 to: 已重命名为 el:。如果你使用位置参数,需要更新如下:

php
// 之前 (v3) - 位置参数
$this->stream('#container', 'Hello');

// 之后 (v4) - 位置/命名参数
$this->stream('Hello', el: '#container');

了解更多关于流式传输 →

组件挂载(内部):

如果你正在扩展 LivewireManager 或直接调用 mount() 方法:

php
// 之前 (v3)
public function mount($name, $params = [], $key = null)

// 之后 (v4)
public function mount($name, $params = [], $key = null, $slots = [])

此更改添加了挂载组件时传递插槽的支持,通常不会影响大多数应用程序。

低影响更改

这些更改只影响使用高级功能或自定义的应用程序。

JavaScript 弃用

弃用:$wire.$js() 方法

用于定义 JavaScript 操作的 $wire.$js() 方法已被弃用:

js
// 弃用 (v3)
$wire.$js('bookmark', () => {
    // 切换书签...
})

// 新 (v4)
$wire.$js.bookmark = () => {
    // 切换书签...
}

新语法更简洁、更直观。

弃用:不带前缀的 $js

在脚本中不带 $wire.$jsthis.$js 前缀使用 $js 已被弃用:

js
// 弃用 (v3)
$js('bookmark', () => {
    // 切换书签...
})

// 新 (v4)
$wire.$js.bookmark = () => {
    // 切换书签...
}
// 或
this.$js.bookmark = () => {
    // 切换书签...
}

旧语法仍然有效

$wire.$js('bookmark', ...)$js('bookmark', ...) 在 v4 中将继续用于向后兼容,但你应该在方便时迁移到新语法。

弃用:commitrequest 钩子

commitrequest 钩子已被弃用,取而代之的是新的拦截器系统,提供更细粒度的控制和更好的性能。

旧钩子仍然有效

弃用的钩子在 v4 中将继续用于向后兼容,但你应该在方便时迁移到新系统。

commit 钩子迁移

旧的 commit 钩子:

js
// 旧 - 弃用
Livewire.hook('commit', ({ component, commit, respond, succeed, fail }) => {
    respond(() => {
        // 收到响应后但处理前运行
    })

    succeed(({ snapshot, effects }) => {
        // 成功响应后运行
    })

    fail(() => {
        // 请求失败时运行
    })
})

应替换为新的 interceptMessage

js
// 新 - 推荐
Livewire.interceptMessage(({ component, message, onFinish, onSuccess, onError, onFailure }) => {
    onFinish(() => {
        // 等同于 respond()
    })

    onSuccess(({ payload }) => {
        // 等同于 succeed()
        // 通过 payload.snapshot 访问 snapshot
        // 通过 payload.effects 访问 effects
    })

    onError(() => {
        // 等同于服务器错误的 fail()
    })

    onFailure(() => {
        // 等同于网络错误的 fail()
    })
})

request 钩子迁移

旧的 request 钩子:

js
// 旧 - 弃用
Livewire.hook('request', ({ url, options, payload, respond, succeed, fail }) => {
    respond(({ status, response }) => {
        // 收到响应时运行
    })

    succeed(({ status, json }) => {
        // 成功响应时运行
    })

    fail(({ status, content, preventDefault }) => {
        // 失败响应时运行
    })
})

应替换为新的 interceptRequest

js
// 新 - 推荐
Livewire.interceptRequest(({ request, onResponse, onSuccess, onError, onFailure }) => {
    // 通过 request.uri 访问 url
    // 通过 request.options 访问 options
    // 通过 request.payload 访问 payload

    onResponse(({ response }) => {
        // 等同于 respond()
        // 通过 response.status 访问 status
    })

    onSuccess(({ response, responseJson }) => {
        // 等同于 succeed()
        // 通过 response.status 访问 status
        // 通过 responseJson 访问 json
    })

    onError(({ response, responseBody, preventDefault }) => {
        // 等同于服务器错误的 fail()
        // 通过 response.status 访问 status
        // 通过 responseBody 访问 content
    })

    onFailure(({ error }) => {
        // 等同于网络错误的 fail()
    })
})

关键差异

  1. 更细粒度的错误处理:新系统将网络故障(onFailure)与服务器错误(onError)分开
  2. 更好的生命周期钩子:消息拦截器提供额外的钩子,如 onSynconMorphonRender
  3. 取消支持:消息和请求都可以被取消/中止
  4. 组件作用域:消息拦截器可以使用 $wire.intercept(...) 限定到特定组件

有关新拦截器系统的完整文档,请参阅 JavaScript 拦截器文档

升级 Volt

Livewire v4 现在支持单文件组件,使用与 Volt 基于类的组件相同的语法。这意味着你可以从 Volt 迁移到 Livewire 内置的单文件组件。

更新组件导入

将所有 Livewire\Volt\Component 实例替换为 Livewire\Component

php
// 之前 (Volt)
use Livewire\Volt\Component;

new class extends Component { ... }

// 之后 (Livewire v4)
use Livewire\Component;

new class extends Component { ... }

移除 Volt 服务提供者

删除 Volt 服务提供者文件:

bash
rm app/Providers/VoltServiceProvider.php

然后从 bootstrap/providers.php 的 providers 数组中移除它:

php
// 之前
return [
    App\Providers\AppServiceProvider::class,
    App\Providers\VoltServiceProvider::class,
];

// 之后
return [
    App\Providers\AppServiceProvider::class,
];

移除 Volt 包

卸载 Volt 包:

bash
composer remove livewire/volt

安装 Livewire v4

完成上述更改后,安装 Livewire v4。你现有的 Volt 基于类的组件将无需修改即可工作,因为它们使用与 Livewire 单文件组件相同的语法。

v4 中的新功能

Livewire v4 引入了几个强大的新功能,你可以立即开始使用:

组件功能

单文件和多文件组件

v4 在传统的基于类的方法之外引入了新的组件格式。单文件组件将 PHP 和 Blade 组合在一个文件中,而多文件组件将 PHP、Blade、JavaScript 和测试组织在一个目录中。

默认情况下,基于视图的组件文件以 ⚡ 表情符号为前缀,以在编辑器和搜索中将它们与常规 Blade 文件区分开来。这可以通过 make_command.emoji 配置禁用。

bash
php artisan make:livewire create-post        # 单文件(默认)
php artisan make:livewire create-post --mfc  # 多文件
php artisan livewire:convert create-post     # 在格式之间转换

了解更多关于组件格式 →

插槽和属性转发

组件现在支持插槽和使用 的自动属性包转发,使组件组合更加灵活。

了解更多关于嵌套组件 →

基于视图组件中的 JavaScript

基于视图的组件现在可以包含 <script> 标签,无需 @script 包装器。这些脚本作为单独的缓存文件提供,具有更好的性能和自动 $wire 绑定:

blade
<div>
    <!-- 你的组件模板 -->
</div>

<script>
    // $wire 自动绑定为 'this'
    this.count++  // 与 $wire.count++ 相同

    // 如果首选,$wire 仍然可用
    $wire.save()
</script>

了解更多关于组件中的 JavaScript →

Islands

Islands 允许你在组件内创建独立更新的隔离区域,在不创建单独子组件的情况下显著提高性能。

blade
@island(name: 'stats', lazy: true)
    <div>{{ $this->expensiveStats }}</div>
@endisland

了解更多关于 Islands →

加载改进

延迟加载

除了懒加载(基于视口),组件现在可以延迟到初始页面加载后立即加载:

blade
<livewire:revenue defer />
php
#[Defer]
class Revenue extends Component { ... }

捆绑加载

控制多个懒加载/延迟组件是并行加载还是捆绑在一起加载:

blade
<livewire:revenue lazy.bundle />
<livewire:expenses defer.bundle />
php
#[Lazy(bundle: true)]
class Revenue extends Component { ... }

了解更多关于懒加载和延迟加载 →

异步操作

使用 .async 修饰符或 #[Async] 属性并行运行操作而不阻塞其他请求:

blade
<button wire:click.async="logActivity">Track</button>
php
#[Async]
public function logActivity() { ... }

了解更多关于异步操作 →

新指令和修饰符

wire:sort - 拖放排序

内置支持带有拖放功能的可排序列表:

blade
<ul wire:sort="updateOrder">
    @foreach ($items as $item)
        <li wire:sort:item="{{ $item->id }}" wire:key="{{ $item->id }}">{{ $item->name }}</li>
    @endforeach
</ul>

了解更多关于 wire:sort →

wire:intersect - 视口交叉

当元素进入或离开视口时运行操作,类似于 Alpine 的 x-intersect

blade
<!-- 基本用法 -->
<div wire:intersect="loadMore">...</div>

<!-- 带修饰符 -->
<div wire:intersect.once="trackView">...</div>
<div wire:intersect:leave="pauseVideo">...</div>
<div wire:intersect.half="loadMore">...</div>
<div wire:intersect.full="startAnimation">...</div>

<!-- 带选项 -->
<div wire:intersect.margin.200px="loadMore">...</div>
<div wire:intersect.threshold.50="trackScroll">...</div>

可用修饰符:

  • .once - 只触发一次
  • .half - 等待一半可见
  • .full - 等待完全可见
  • .threshold.X - 自定义可见百分比(0-100)
  • .margin.Xpx.margin.X% - 交叉边距

了解更多关于 wire:intersect →

wire:ref - 元素引用

轻松在模板中引用和交互元素:

blade
<div wire:ref="modal">
    <!-- 模态框内容 -->
</div>

<button wire:click="$js.scrollToModal">Scroll to modal</button>

<script>
    this.$js.scrollToModal = () => {
        this.$refs.modal.scrollIntoView()
    }
</script>

了解更多关于 wire:ref →

.renderless 修饰符

直接从模板跳过组件重新渲染:

blade
<button wire:click.renderless="trackClick">Track</button>

这是 #[Renderless] 属性的替代方案,用于不需要更新 UI 的操作。

.preserve-scroll 修饰符

在更新期间保持滚动位置以防止布局跳动:

blade
<button wire:click.preserve-scroll="loadMore">Load More</button>

data-loading 属性

每个触发网络请求的元素都会自动接收 data-loading 属性,使用 Tailwind 轻松设置加载状态样式:

blade
<button wire:click="save" class="data-loading:opacity-50 data-loading:pointer-events-none">
    Save Changes
</button>

了解更多关于加载状态 →

JavaScript 改进

$errors 魔术属性

从 JavaScript 访问组件的错误包:

blade
<div wire:show="$errors.has('email')">
    <span wire:text="$errors.first('email')"></span>
</div>

了解更多关于验证 →

$intercept 魔术

从 JavaScript 拦截和修改 Livewire 请求:

blade
<script>
this.$intercept('save', ({ ... }) => {
    // ...
})
</script>

了解更多关于 JavaScript 拦截器 →

从 JavaScript 定向 Island

直接从模板触发 Island 渲染:

blade
<button wire:click="loadMore" wire:island.append="stats">
    Load more
</button>

了解更多关于 Islands →

获取帮助

如果在升级过程中遇到问题: