Skip to content

从 v3 升级到 v4

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

Livewire v4 目前处于 beta 阶段

Livewire v4 仍在积极开发中,尚未稳定。建议在升级生产应用程序之前在开发环境中进行充分测试。beta 版本之间可能会发生破坏性更改。

平滑的升级路径

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

安装

更新你的 composer.json 以要求 Livewire v4 beta:

bash
composer require livewire/livewire:^4.0@beta

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

bash
php artisan config:clear
php artisan view:clear

在 GitHub 上查看所有更改

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

高影响更改

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

配置文件更新

多个配置键已被重命名、重组或具有新的默认值。更新你的 config/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,

启用内容安全策略模式以避免 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() 现在是首选方法,并且是单文件和多文件组件作为全页面组件正确工作所必需的。

了解更多关于路由 →

中等影响更改

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

性能改进

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

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

这些改进自动发生——你的代码不需要任何更改。

方法签名更改

如果你正在扩展 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 = [])

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

低影响更改

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

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

旧语法仍然有效

$wire.$js('bookmark', ...)$js('bookmark', ...) 都将在 v4 中继续工作以保持向后兼容性,但你应该在方便时迁移到新语法。

弃用: commitrequest 钩子

commitrequest 钩子已被弃用,以支持提供更细粒度控制和更好性能的新拦截器系统。

旧钩子仍然有效

弃用的钩子将在 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. 组件作用域:拦截器可以使用 Livewire.intercept($wire, ...) 限定为特定组件

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

v4 中的新功能

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

组件功能

Single-file and multi-file components

v4 introduces new component formats alongside the traditional class-based approach. Single-file components combine PHP and Blade in one file, while multi-file components organize PHP, Blade, JavaScript, and tests in a directory.

By default, view-based component files are prefixed with a ⚡ emoji to distinguish them from regular Blade files in your editor and searches. This can be disabled via the make_command.emoji config.

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 and attribute forwarding

Components now support slots and automatic attribute bag forwarding using , making component composition more flexible.

了解更多关于嵌套组件 →

JavaScript in view-based components

View-based components can now include <script> tags without the @script wrapper. These scripts are served as separate cached files for better performance and automatic $wire binding:

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 allow you to create isolated regions within a component that update independently, dramatically improving performance without creating separate child components.

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

Islands also support imperative rendering and streaming from your component actions.

Learn more about islands →

加载改进

Deferred loading

In addition to lazy loading (viewport-based), components can now be deferred to load immediately after the initial page load:

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

Bundled loading

Control whether multiple lazy/deferred components load in parallel or bundled together:

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

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

异步操作

Run actions in parallel without blocking other requests using the .async modifier or #[Async] attribute:

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

了解更多关于异步操作 →

新指令和修饰符

wire:sort - Drag-and-drop sorting

Built-in support for sortable lists with drag-and-drop:

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

了解更多关于 wire:sort →

wire:intersect - Viewport intersection

Run actions when elements enter or leave the viewport, similar to Alpine's 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>

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

Available modifiers:

  • .once - Fire only once
  • .half - Wait until half is visible
  • .full - Wait until fully visible
  • .threshold.X - Custom visibility percentage (0-100)
  • .margin.Xpx or .margin.X% - Intersection margin

了解更多关于 wire:intersect →

wire:ref - Element references

Easily reference and interact with elements in your template:

blade
@foreach ($comments as $comment)
    <div wire:ref="comment-{{ $comment->id }}">
        {{ $comment->body }}
    </div>
@endforeach

<button wire:click="$refs['comment-123'].scrollIntoView()">
    Scroll to Comment
</button>

了解更多关于 wire:ref →

.renderless modifier

Skip component re-rendering directly from the template:

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

This is an alternative to the #[Renderless] attribute for actions that don't need to update the UI.

了解更多关于操作 →

.preserve-scroll modifier

Preserve scroll position during updates to prevent layout jumps:

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

data-loading attribute

Every element that triggers a network request automatically receives a data-loading attribute, making it easy to style loading states with Tailwind:

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

了解更多关于加载状态 →

JavaScript 改进

$errors magic property

Access your component's error bag from JavaScript:

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

了解更多关于验证 →

$intercept magic

Intercept and modify Livewire requests from JavaScript:

blade
<script>
this.$intercept('save', ({ proceed }) => {
    if (confirm('Save changes?')) {
        proceed()
    }
})
</script>

了解更多关于 JavaScript 拦截器 →

Island targeting from JavaScript

Trigger island renders directly from the template:

blade
<button wire:click="$refresh" wire:island.prepend="stats">
    Update Stats
</button>

了解更多关于 islands →

获取帮助

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