You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为多个Livewire 3组件构建可复用的排序与分页逻辑

多个Livewire 3组件构建可复用的排序与分页逻辑

我太懂你这种痛点了——写完一个带排序分页的食谱列表组件后,又要做点赞、收藏的同款列表,重复写逻辑不仅麻烦,后续改需求时要改好几处,简直是维护噩梦。刚好在Livewire里有非常优雅的解决方式,我给你分享一套实际项目里在用的方案:

核心思路:用PHP Trait抽离通用逻辑

Trait是PHP里复用代码的绝佳工具,尤其适合这种多个组件需要相同功能,但又不想被单继承限制灵活性的场景。我们把排序、分页的通用逻辑封装成一个Trait,所有需要的组件直接引入就能用。

第一步:创建复用的Trait文件

先在app/Http/Livewire/Traits目录下新建WithSortingAndPagination.php(没有目录就自己建一个),把通用的属性和方法都放进去:

<?php

namespace App\Http\Livewire\Traits;

use Livewire\WithPagination;

trait WithSortingAndPagination
{
    // 先引入Livewire自带的分页Trait
    use WithPagination;

    // 默认排序字段,可根据需求修改
    public $sortField = 'created_at';
    // 默认排序方向
    public $sortDirection = 'desc';
    // 默认每页显示数量
    public $perPage = 10;

    // 切换排序方向的方法,点击表头时调用
    public function sortBy($field)
    {
        if ($this->sortField === $field) {
            // 点击的是当前排序字段,切换排序方向
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            // 点击的是新字段,默认降序排列
            $this->sortDirection = 'desc';
            $this->sortField = $field;
        }
    }

    // 封装排序逻辑,给查询构建器调用
    public function applySorting($query)
    {
        return $query->orderBy($this->sortField, $this->sortDirection);
    }
}

第二步:改造原有的RecipeList组件

现在把RecipeList.php里的排序分页逻辑删掉,直接引入我们的Trait,代码瞬间清爽很多:

<?php

namespace App\Http\Livewire;

use App\Http\Livewire\Traits\WithSortingAndPagination;
use Livewire\Component;
use App\Models\Recipe;

class RecipeList extends Component
{
    // 引入Trait,直接拥有排序分页功能
    use WithSortingAndPagination;

    // 保留你原来的筛选参数
    public $category = '';
    public $search = '';

    public function render()
    {
        $recipes = Recipe::query()
            ->when($this->category, fn($q) => $q->where('category', $this->category))
            ->when($this->search, fn($q) => $q->where('title', 'like', "%{$this->search}%"))
            // 调用Trait里的排序方法
            ->tap([$this, 'applySorting'])
            // 直接用Trait里的perPage属性分页
            ->paginate($this->perPage);

        return view('livewire.recipe-list', compact('recipes'));
    }
}

第三步:快速创建新的Liked/Saved食谱列表组件

现在搞点赞和收藏的列表组件就太简单了,比如LikedRecipeList.php:

<?php

namespace App\Http\Livewire;

use App\Http\Livewire\Traits\WithSortingAndPagination;
use Livewire\Component;
use App\Models\Recipe;

class LikedRecipeList extends Component
{
    // 同样引入Trait,直接拥有排序分页功能
    use WithSortingAndPagination;

    public $user;

    public function mount()
    {
        $this->user = auth()->user();
    }

    public function render()
    {
        $likedRecipes = $this->user->likedRecipes()
            // 调用Trait的排序方法
            ->tap([$this, 'applySorting'])
            // 直接分页
            ->paginate($this->perPage);

        return view('livewire.liked-recipe-list', compact('likedRecipes'));
    }
}

SavedRecipeList的写法几乎一模一样,只要把关联关系改成savedRecipes()就行,完全不用重复写排序分页的代码。

第四步:复用视图里的排序分页UI

光后端逻辑复用还不够,前端的按钮、分页控件也可以抽成Blade组件复用。比如创建resources/views/components/sort-pagination-controls.blade.php:

<div class="flex justify-between items-center mb-4">
    <!-- 排序按钮组 -->
    <div class="space-x-2">
        <button wire:click="sortBy('title')" class="px-3 py-1 rounded {{ $sortField === 'title' ? 'bg-blue-500 text-white' : 'bg-gray-100' }}">
            标题 {{ $sortField === 'title' ? ($sortDirection === 'asc' ? '↑' : '↓') : '' }}
        </button>
        <button wire:click="sortBy('created_at')" class="px-3 py-1 rounded {{ $sortField === 'created_at' ? 'bg-blue-500 text-white' : 'bg-gray-100' }}">
            创建时间 {{ $sortField === 'created_at' ? ($sortDirection === 'asc' ? '↑' : '↓') : '' }}
        </button>
    </div>

    <!-- 每页数量选择 -->
    <div>
        <select wire:model="perPage" class="px-3 py-1 border rounded">
            <option value="10">10条/页</option>
            <option value="20">20条/页</option>
            <option value="50">50条/页</option>
        </select>
    </div>
</div>

<!-- Livewire分页组件 -->
<div class="mt-6">
    {{ $items->links() }}
</div>

然后在各个列表视图里直接引入这个组件就行,比如livewire/recipe-list.blade.php:

<div>
    <!-- 你的筛选表单,保留原来的逻辑 -->
    <div class="mb-4">
        <input wire:model.live="search" placeholder="搜索食谱..." class="px-4 py-2 border rounded">
        <select wire:model.live="category" class="px-4 py-2 border rounded ml-2">
            <option value="">全部分类</option>
            <!-- 你的分类选项 -->
        </select>
    </div>

    <!-- 复用排序分页控件 -->
    <x-sort-pagination-controls :items="$recipes" />

    <!-- 食谱列表内容 -->
    <div class="grid grid-cols-3 gap-4">
        @foreach($recipes as $recipe)
            <div class="border rounded p-4">
                <h3 class="font-bold">{{ $recipe->title }}</h3>
                <!-- 其他食谱信息 -->
            </div>
        @endforeach
    </div>

    <!-- 底部再放一次分页控件(可选) -->
    <x-sort-pagination-controls :items="$recipes" />
</div>

这样不管是哪个列表页面,UI风格统一,还不用重复写HTML代码。

为什么选Trait而不是父类?

相比用父类继承,Trait的灵活性更高——PHP只支持单继承,如果你以后还要加别的通用逻辑(比如通用筛选),可以再抽成另一个Trait,组件可以同时引入多个,完全不会有继承冲突的问题。而且如果某个组件需要特殊定制排序逻辑,直接在组件里重写Trait里的方法就行,非常方便。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 13:37:59