【问题标题】:Laravel Livewire wire:click creates infinite loopLaravel Livewire 电线:单击创建无限循环
【发布时间】:2021-02-20 04:52:31
【问题描述】:

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

您可以在下面的 GIF 中看到发生的事情,该 GIF 是 Laravel 调试栏的捕获。我点击了一些过滤器,然后突然进入了这个请求循环。

我在视图中的过滤器上专门使用了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

这很奇怪,因为这个刀片 sn-p 与 $brands 完全相同,如上所示:

唯一不同的是colors 关系是hasMany 与belongsTo 对应brand。

我现在认为这就是问题所在......

我尝试过但没用的东西

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

控制台错误

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

TypeError: null is not an object (evaluating 'directive.value.split')

Unhandled Promise Rejection: TypeError: null is not an object (evaluating '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)

【问题讨论】:

  • 您在循环中使用 wire:model="brand" ,并且每当复选框输入值更改时,这将触发新的 ajax 请求以重新渲染视图,然后 foreach 循环将更改复选框输入值触发另一个 ajax 等等。
  • 谢谢。我不认为我理解“foreach 循环将更改复选框输入值”。由于 Livewire 的工作方式,带有wire:model="brand" 的复选框将使该数组在整个请求中保持同步?
  • 我的意思是复选框的“value”属性,value="{{ $brand->id }}" 这将在循环时发生变化,并且模型正在监听变化。我看到了你的回答,是的,我认为这会解决你的问题。

标签: laravel laravel-livewire


【解决方案1】:

在 GitHub issue 上回答,复制到这里供其他人找到。


问题是形态问题。

触发错误和循环的代码是电线:加载标题行和产品行。

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

但是 morphdom 默认不知道它应该删除旧的 div 并添加新的 div。相反,它试图将旧的第一个 div “变形”为新的。这意味着 wire:loading 监听器在不应该注册的时候仍然被注册。因此为什么会出现错误和循环。

虽然这是一个简单的修复。您需要将线键添加到定义它们是什么的 div 中,以便 morphdom 知道它们实际上已经完全改变,并删除旧的并添加新的。

查看下面的差异屏幕截图,了解我为使其正常工作所做的工作。我为这个文件中的所有顶级 div 添加了一个线键。

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

Screenshot of diff

【讨论】:

  • 对于阅读本文的人:我的 Livewire 组件中有另一个地方有一个循环。此答案中描述的是在该循环之外添加一个线键,以便 Livewire 可以跟踪。简而言之:我们找错了地方(像往常一样)。谢谢乔希!
【解决方案2】:

所以事实证明,如果你在这样的foreach 循环中使用wire:model,你必须这样写:wire:model="brand.{{ $brand-&gt;id }}"。在文档中找不到它,所以希望它可以帮助其他人。

更新

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

更新 2

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

【讨论】:

  • 不应该是brand.{{ $loop-&gt;index }} 吗?至少,当您使用带有wire:model 的数据数组时,它希望有索引。我们可以看到这个here in the doc
  • 否,因为在这种情况下,brands 是一个数组,而不是模型集合。与所有品牌变量和未记录此数组表示法有点混淆。
  • 在a给你的例子中,如果你滚动一点,它使用$index的循环和Post的集合
  • 是的,但是品牌数组值将是循环计数而不是它们应该是的品牌 ID。正如您在组件中看到的那样,传递数组的值以执行查询。所以它们需要是品牌 ID,而不是循环计数。
【解决方案3】:

我看到的几个问题:

您对&lt;div&gt; 元素和&lt;input&gt; 元素使用相同的wire:key。我会保留那些独一无二的。

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

【讨论】:

  • 谢谢,我试试这个。但是,我确实完全删除了 name 字段(以及删除了 wire:key),但不确定我做了哪些组合,目前没有尝试,所以我会正确测试。
  • 不走运。我尝试仅在div 上使用wire:key 并删除name 字段,然后我尝试删除wire:key 并使用name="brand.{{ brand-&gt;id }}",但两种组合我仍然得到循环。
  • 我已经添加了最后一次更新,如果你能看一下,将不胜感激!
  • 我已经添加了最后一次更新,如果你能看一下,将不胜感激!
  • $colors 的设置是否与$brands 完全相同?我注意到您将 $all_brands 更改为 $brands。另外,当您在输入字段中没有wire:loading.attr="disabled" 时,您是否遇到过这个问题?不用那个你可以试试吗?
猜你喜欢
  • 2021-08-29
  • 1970-01-01
  • 2021-01-13
  • 1970-01-01
  • 1970-01-01
  • 2021-02-20
  • 2021-07-04
  • 1970-01-01
  • 2021-04-10
相关资源
最近更新 更多