开发者问题收集

Laravel Livewire wire:click 创建无限循环

2020-11-07
4162

我有一个 Livewire 组件,它是一个产品过滤器。查询都运行正常,但有时它会创建无限循环的请求。

您可以在下面的 GIF 中看到这种情况,它是 Laravel 调试栏的捕获。我正在单击一些过滤器,然后突然进入此请求循环。

enter image description here

我专门在视图中的过滤器上使用 wire:loading.attr="disabled" ,因此在请求仍在处理时,有人无法选择过滤器。

我的代码和一些背景:

Livewire 组件

use App\Models\Product;
use App\Models\Brand;
use App\Models\Color;

class SearchProducts extends Component
{
    public ?array $brand = [];
    public ?array $color = [];

    protected $queryString = ['brand', 'color'];

    public function render()
    {
        $products = Product::query();

        $products = $products->with('brand');
        $products = $products->with('colors');

        $products = $this->filterBrands($products);
        $products = $this->filterColors($products);

        $products = $products->paginate(24);

        return view('livewire.search-products', [
            'all_brands' => Brand::where('status', 'active')->get(),
            'all_colors' => Color::where('status', 'active')->get(),
        ])->extends('app');
    }

    public function filterBrands($query)
    {
        $queryFilterBrand = array_filter($this->brand);
        
        return empty($queryFilterBrand) ? $query : $query->whereIn('brand_id', $queryFilterBrand);
    }

    public function filterColors($query)
    {
        $queryFilterColor = array_filter($this->color);

        return empty($queryFilterColor) ? $query : $query->whereHas('colors', function ($q) use ($queryFilterColor) {
            $q->whereIn('color_id', $queryFilterColor);
        });
    }

}

我使用 array_filter 的原因是因为如果我取消选择颜色值并在键中使用字符( wire:model="brand.b{{ $brand->id }}" ),而不是从数组中删除它Livewire 会将该键值设置为 false 。因此,此 false 值将被放入查询中,从而产生不准确的结果。

Livewire 视图和问题

这可以正常工作:

@foreach($all_brands as $brand)
    <input type="checkbox" value="{{ $brand->id }}" id="brand.{{ $brand->id }}" wire:model="brand.{{ $brand->id }}" wire:loading.attr="disabled">
    <label class="search-label search-wide-label mb-2" for="brand.{{ $brand->id }}">{{ $brand->title }} <i class="fal fa-times float-right selected-icon"></i></label>
@endforeach

但是,当我连续选择 2 种或更多种颜色时,或者当我选择 1 种颜色然后取消选择时,这将产生无限循环。因此,问题似乎发生在第二次交互之后:

@foreach($all_colors as $color)
    <input type="checkbox" value="{{ $color->id }}" id="color.{{ $color->id }}" wire:model="color.{{ $color->id }}" wire:loading.attr="disabled">
    <label class="search-label search-wide-label mb-2" for="color.{{ $color->id }}">{{ $color->title }} <i class="fal fa-times float-right selected-icon"></i></label>
@endforeach

这很奇怪,因为此 blade 代码段与上面显示的 $brands 完全相同:

唯一不同的是 colors 关系是 hasMany ,而不是 brandbelongsTo

我现在认为问题就在这里……

我尝试过但没有奏效的方法

  • 删除 $all_colors@foreach 循环,然后将过滤器放在纯 HTML 中(以检查问题是否与循环有关)
  • wire:key="brand.{{ $brand->id }}" 添加到 input 元素
  • input 元素周围的 div 中添加 wire:key="brand.{{ $brand->id }}"
  • 使用 wire:model="brand.{{ $brand->id }}"wire:model="brand.{{ $loop->id }}" ,如评论中所建议(我认为这解决了问题)
  • 使用 wire:model="brand.b{{ $brand->id }}" ,这样就有了一个唯一的键名
  • 删除 array_filter 方法(这似乎不太可能是问题,但只是为了测试)
  • 使用按钮而不是复选框
  • 使用 deferlazy 和/或 debounce
  • 请专家尝试修复它...

控制台错误

最后一点,我的控制台中 只有当无限循环发生时 才会出现此错误,因此它很可能是原因或结果。

TypeError:null 不是对象(评估“directive.value.split”)

未处理的承诺拒绝:TypeError:null 不是对象(评估“directive.value.split”)

两者都在 LoadingStates.js 中,我认为它是一个 Livewire Javascript 文件。

那里的错误似乎发生在这里:

function startLoading(els) {
    els.forEach(({ el, directive }) => {
        if (directive.modifiers.includes('class')) {
            let classes = directive.value.split(' ').filter(Boolean)
3个回答

在 GitHub 问题上回答,复制到此处以便其他人能够找到。


问题是 morphdom 问题。

触发错误和循环的代码是标题行和产品行上的 wire:loading。

原因是,当您选择两种或更多种颜色时,没有显示任何结果。然后发生的事情是您从显示标题/产品/总计切换到显示空状态。

但 morphdom 默认情况下不知道它应该删除旧的 div 并添加新的 div。相反,它试图将旧的第一个 div“变形”为新的 div。这意味着 wire:loading 侦听器仍然注册,而它们不应该注册。这就是错误和循环的原因。

不过这是一个简单的修复。您需要向 div 添加 wire key 来定义它们是什么,这样 morphdom 就知道它们实际上已经完全改变了,并删除旧的并添加新的。

查看下面的 diff 屏幕截图,了解我为使其工作所做的工作。我为该文件中的所有顶级 div 添加了 wire key。

建议在使用此类条件时,将 wire:keys 添加到条件中第一级的任何元素,这样 morphdom 就知道发生了变化。这与 VueJS 存在的问题相同,在循环内需要键。

diff 屏幕截图

Josh Hanley
2020-12-29

因此,事实证明,如果您在 foreach 循环中使用 wire:model ,则必须像这样编写: wire:model="brand.{{ $brand->id }}" 。在文档中找不到它,所以希望它能帮助其他人。

更新

无限循环已通过此方法解决,但现在发生的情况是,一旦您选中复选框,然后再次单击它以取消选择,数组值将被设置为零,而不是从数组中删除。因此, whereIn 将查找值为 0 的品牌 ID,这不会返回结果。

更新 2

循环实际上并未解决...请参阅原始问题。为此 $%$£# 悬赏,因为浪费了太多时间和咖啡。

eskimo
2020-11-08

我发现几个问题:

您对 <div> 元素和 <input> 元素使用了相同的 wire:key。我会保持它们的唯一性。

此外,您在输入元素上使用相同的 name 。这会导致复选框出现问题,因为传递给后端的值具有相同的名称,我相信 livewire 会尝试基于此进行同步。将 input 中的 name 字段更改为唯一的字段可能是答案。

shortbrownman
2020-12-10