Skip to content
全部文档

升级指南

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

TIP

平滑升级路径

多数应用只需极少改动即可升级到 v4。破坏性变更主要是配置更新,以及仅影响高级用法的方法签名变更。

想节省时间?可以使用 Laravel Shift 帮助自动化应用升级。

安装

更新你的 composer.json,要求使用 Livewire v4:

bash
composer require livewire/livewire:^4.0

更新后,清除应用缓存:

bash
php artisan optimize:clear

INFO

在 GitHub 上查看全部变更

若要完整了解 v3 与 v4 之间的全部代码变更,可在 GitHub 上查看完整 diff:Compare 3.x to main →

从 v4.0 升级到 v4.1

wire:model 修饰符行为变更

.blur.change 等修饰符现在会控制客户端状态何时同步,而不再只影响网络请求时机。若你正在使用这些修饰符并希望保留原先行为,请在它们前面加上 .live(例如 wire:model.live.blur)。

查看下方完整说明 →


以下变更适用于从 v3 升级到 v4。

高影响变更

这些变更最有可能影响你的应用,应仔细核对。

配置文件更新

若干配置键已重命名、重组,或有了新的默认值。请更新你的 config/livewire.php 文件:

TIP

查看完整配置文件

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

已重命名的配置键

布局配置:

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

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

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

占位符配置:

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

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

变更的默认值

智能 wire:key 行为:

php
// Now defaults to true (was false in v3)
'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',  // Options: 'sfc', 'mfc', or 'class'
    'emoji' => true,   // Whether to use ⚡ emoji prefix
],

配置默认组件格式以及是否使用 emoji。将 type 设为 'class' 即可与 v3 行为一致。

CSP 安全模式:

php
'csp_safe' => false,

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

路由变更

对于整页组件,推荐的路由写法已变更:

php
// Before (v3) - still works but not recommended
Route::get('/dashboard', Dashboard::class);

// After (v4) - recommended for all component types
Route::livewire('/dashboard', Dashboard::class);

// For view-based components, you can use the component name
Route::livewire('/dashboard', 'pages::dashboard');

现在推荐使用 Route::livewire(),且单文件与多文件组件要作为整页组件正确工作,必须使用该方法。

了解路由 →

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

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

在 v4 中,wire:model 现在只监听直接来自元素自身的事件(等同于 .self 修饰符的行为)。

若你的代码依赖捕获来自子元素的事件,请添加 .deep 修饰符:

blade
<!-- Before (v3) - listened to child events by default -->
<div wire:model="value">
    <input type="text">
</div>

<!-- After (v4) - add .deep to restore old behavior -->
<div wire:model.deep="value">
    <input type="text">
</div>

TIP

多数应用无需改动

该变更主要影响在容器元素上非常规使用 wire:model 的场景。标准表单输入绑定(input、select、textarea)不受影响。

使用 wire:navigate:scroll

在 v3 中若使用 wire:scrollwire:navigate 请求间保留可滚动容器的滚动位置,在 v4 中需改为使用 wire:navigate:scroll

text
@persist('sidebar')
    <div class="overflow-y-scroll" wire:scroll> <!-- [tl! remove] -->
    <div class="overflow-y-scroll" wire:navigate:scroll> <!-- [tl! add] -->
        <!-- ... -->
    </div>
@endpersist

组件标签必须闭合

在 v3 中,即使 Livewire 组件标签未正确闭合也能渲染。在 v4 中,由于新增了 slot 支持,组件标签必须正确闭合——否则 Livewire 会把后续内容当作 slot 内容,组件将无法渲染:

blade
<!-- Before (v3) - unclosed tag -->
<livewire:component-name>

<!-- After (v4) - Self-closing tag -->
<livewire:component-name />

了解渲染组件 →

了解 slots →

中等影响变更

这些变更可能影响应用中使用相关功能的部分。

wire:model 修饰符现在控制客户端同步时机

在 v3 中,.blur.change 等修饰符只控制何时发送网络请求。输入值会在用户输入时立即同步到客户端状态($wire.property)。

在 v4 中,这些修饰符也会控制客户端状态何时同步。这解锁了新的 UI 模式——例如输入框在用户完成输入并按 Enter 或切走焦点之前不会更新。

迁移: 若你正在使用 .blur.change 并希望保留旧行为,请在修饰符前加上 .live

blade
<!-- v3 -->
<input wire:model.blur="title">

<!-- v4 equivalent -->
<input wire:model.live.blur="title">
v3 语法v4 等价写法
wire:model.blurwire:model.live.blur
wire:model.changewire:model.live.change

INFO

.lazy 向后兼容

wire:model.lazy 的行为与 v3 一致——无需迁移。

v4 新特性: 你现在可以延迟客户端更新,且不发送网络请求:

blade
<!-- Only update $wire.width when user tabs away -->
<input wire:model.blur="width">

<!-- Update on Enter key or blur -->
<input wire:model.blur.enter="search">

了解 wire:model →

wire:transition 现在使用 View Transitions API

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

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

blade
<!-- This still works in v4 -->
<div wire:transition>...</div>

<!-- These modifiers are no longer supported -->
<div wire:transition.opacity>...</div> <!-- [tl! remove] -->
<div wire:transition.scale.origin.top>...</div> <!-- [tl! remove] -->
<div wire:transition.duration.500ms>...</div> <!-- [tl! remove] -->

了解 wire:transition →

性能改进

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

  • 非阻塞轮询wire:poll 不再阻塞其他请求,也不会被其他请求阻塞
  • 并行 live 更新wire:model.live 请求现在并行执行,输入更快、结果更及时

这些改进会自动生效——你的代码无需改动。

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

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

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

之后: 同样的操作只会带着完整的新数组值触发一次钩子,与 v2 行为一致。

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

方法签名变更

若你在扩展 Livewire 核心功能或直接使用这些方法,请注意以下签名变更:

流式输出:

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

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

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

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

php
// Before (v3) - positional parameters
$this->stream('#container', 'Hello');

// After (v4) - positional/named parameters
$this->stream('Hello', el: '#container');

了解流式输出 →

组件挂载(内部):

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

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

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

该变更增加了挂载组件时传递 slots 的支持,通常不会影响大多数应用。

低影响变更

这些变更仅影响使用高级功能或自定义的应用。

wire:model 现在支持方括号写法

wire:model 表达式现在支持用方括号写法访问嵌套属性:

blade
<input type="text" wire:model="foo['bar']['baz']">

<input type="text" wire:model="items[0].name">

这意味着 wire:model 值中的方括号([])现在会被解释为属性访问器。在 v3 中,它们被当作字面字符。若你的属性键包含方括号,请重命名以避免冲突。

了解 wire:model →

Livewire 资源与端点 URL 变更

所有 Livewire URL 现在都会包含由你的 APP_KEY 派生的唯一哈希。前缀从 /livewire/ 变为 /livewire-{hash}/

text
# v3                          # v4
/livewire/update        →     /livewire-{hash}/update
/livewire/upload-file   →     /livewire-{hash}/upload-file
/livewire/livewire.js   →     /livewire-{hash}/livewire.js

若你的防火墙规则、CDN 配置或中间件引用了 /livewire/ 路径,请更新以适配新前缀。

若你在使用 setUpdateRoute,请使用 $path 参数以保留基于哈希的端点:

php
// Before (v3)
Livewire::setUpdateRoute(function ($handle) {
    return Route::post('/livewire/update', $handle);
});

// After (v4)
Livewire::setUpdateRoute(function ($handle, $path) {
    return Route::post($path, $handle);
});

了解如何自定义 Livewire 端点 →

JavaScript 弃用

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

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

js
// Deprecated (v3)
$wire.$js('bookmark', () => {
    // Toggle bookmark...
})

// New (v4)
$wire.$js.bookmark = () => {
    // Toggle bookmark...
}

新语法更简洁、更直观。

已弃用:无前缀的 $js

在脚本中不带 $wire.$jsthis.$js 前缀而直接使用 $js 的写法已弃用:

js
// Deprecated (v3)
$js('bookmark', () => {
    // Toggle bookmark...
})

// New (v4)
$wire.$js.bookmark = () => {
    // Toggle bookmark...
}
// Or
this.$js.bookmark = () => {
    // Toggle bookmark...
}

TIP

旧语法仍然可用

出于向后兼容,$wire.$js('bookmark', ...)$js('bookmark', ...) 在 v4 中仍可使用,但建议你在方便时迁移到新语法。

已弃用:commitrequest 钩子

commitrequest 钩子已弃用,请改用新的拦截器系统,它能提供更细粒度的控制与更好的性能。

TIP

旧钩子仍然可用

出于向后兼容,已弃用的钩子在 v4 中仍可使用,但建议你在方便时迁移到新系统。

commit 钩子迁移

旧的 commit 钩子:

js
// OLD - Deprecated
Livewire.hook('commit', ({ component, commit, respond, succeed, fail }) => {
    respond(() => {
        // Runs after response received but before processing
    })

    succeed(({ snapshot, effects }) => {
        // Runs after successful response
    })

    fail(() => {
        // Runs if request failed
    })
})

应替换为新的 interceptMessage

js
// NEW - Recommended
Livewire.interceptMessage(({ component, message, onFinish, onSuccess, onError, onFailure }) => {
    onFinish(() => {
        // Equivalent to respond()
    })

    onSuccess(({ payload }) => {
        // Equivalent to succeed()
        // Access snapshot via payload.snapshot
        // Access effects via payload.effects
    })

    onError(() => {
        // Equivalent to fail() for server errors
    })

    onFailure(() => {
        // Equivalent to fail() for network errors
    })
})

request 钩子迁移

旧的 request 钩子:

js
// OLD - Deprecated
Livewire.hook('request', ({ url, options, payload, respond, succeed, fail }) => {
    respond(({ status, response }) => {
        // Runs when response received
    })

    succeed(({ status, json }) => {
        // Runs on successful response
    })

    fail(({ status, content, preventDefault }) => {
        // Runs on failed response
    })
})

应替换为新的 interceptRequest

js
// NEW - Recommended
Livewire.interceptRequest(({ request, onResponse, onSuccess, onError, onFailure }) => {
    // Access url via request.uri
    // Access options via request.options
    // Access payload via request.payload

    onResponse(({ response }) => {
        // Equivalent to respond()
        // Access status via response.status
    })

    onSuccess(({ response, responseJson }) => {
        // Equivalent to succeed()
        // Access status via response.status
        // Access json via responseJson
    })

    onError(({ response, responseBody, preventDefault }) => {
        // Equivalent to fail() for server errors
        // Access status via response.status
        // Access content via responseBody
    })

    onFailure(({ error }) => {
        // Equivalent to fail() for network errors
    })
})

主要差异

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

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

升级 Volt

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

更新组件导入

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

php
// Before (Volt)
use Livewire\Volt\Component;

new class extends Component { ... }

// After (Livewire v4)
use Livewire\Component;

new class extends Component { ... }

更新路由定义

在路由文件中将 Volt::route() 替换为 Route::livewire()

php
// Before (Volt)
use Livewire\Volt\Volt;

Volt::route('/dashboard', 'dashboard');

// After (Livewire v4)
use Illuminate\Support\Facades\Route;

Route::livewire('/dashboard', 'dashboard');

更新测试文件

将所有 Livewire\Volt\Volt 替换为 Livewire\Livewire,并将 Volt::test() 改为 Livewire::test()

php
// Before (Volt)
use Livewire\Volt\Volt;

Volt::test('counter')

// After (Livewire v4)
use Livewire\Livewire;

Livewire::test('counter')

移除 Volt 服务提供者

删除 Volt 服务提供者文件:

bash
rm app/Providers/VoltServiceProvider.php

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

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

// After
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 和测试组织在一个目录中。

默认情况下,基于视图的组件文件会以 ⚡ emoji 作为前缀,以便在编辑器和搜索中与普通 Blade 文件区分。可通过 make_command.emoji 配置关闭。

bash
php artisan make:livewire create-post        # Single-file (default)
php artisan make:livewire create-post --mfc  # Multi-file
php artisan livewire:convert create-post     # Convert between formats

了解组件格式 →

Slots 与属性转发

组件现在支持 slots,以及使用 {{ $attributes }} 的自动属性包转发,使组件组合更加灵活。

了解嵌套组件 →

基于视图组件中的 JavaScript

基于视图的组件现在可以直接包含 <script> 标签,无需 @script 包裹。这些脚本会作为独立的缓存文件提供,以获得更好的性能,并自动绑定 $wire

blade
<div>
    <!-- Your component template -->
</div>

<script>
    // $wire is automatically bound as 'this'
    this.count++  // Same as $wire.count++

    // $wire is still available if preferred
    $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] attribute,可并行运行动作且不阻塞其他请求:

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
<!-- Basic usage -->
<div wire:intersect="loadMore">...</div>

<!-- With modifiers -->
<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.half.dwell.500ms="trackImpression">...</div>

<!-- With options -->
<div wire:intersect.margin.200px="loadMore">...</div>
<div wire:intersect.threshold.50="trackScroll">...</div>
<div wire:intersect.parent="loadWithinScroller">...</div>

可用修饰符:

  • .once - 只触发一次
  • .dwell.Xms - 要求持续交叉一段时间(默认 250ms)
  • .half - 等到一半可见
  • .full - 等到完全可见
  • .threshold.X - 自定义可见百分比(0-100)
  • .margin.Xpx .margin.X% - 交叉边距
  • .parent - 相对于元素的父级观察
  • .renderless - 动作后跳过渲染
  • .async - 并行运行动作
  • .preserve-scroll - 保留页面滚动位置

了解 wire:intersect →

wire:ref - 元素引用

轻松引用并与模板中的元素交互:

blade
<div wire:ref="modal">
    <!-- Modal content -->
</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] attribute 的替代方案,适用于不需要更新 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 访问组件的 error bag:

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 →

获取帮助

若升级过程中遇到问题:

  • 查阅[文档](https://livewire.laravel.com)获取详细功能指南
  • 访问 [GitHub discussions](https://github.com/livewire/livewire/discussions) 获取社区支持