Laravel Livewire wire:click 创建无限循环
Laravel Livewire wire:click creates infinite loop
我有一个 Livewire 组件,它是一个产品过滤器。查询都工作正常,但有时它会创建一个无限循环的请求。
您可以在下面的 GIF 中看到发生的情况,它是 Laravel 调试栏的捕获。我正在单击一些过滤器,然后突然进入此请求循环。
我专门在视图中的过滤器上使用了 wire:loading.attr="disabled"
,因此当请求仍在处理时,有人无法 select 过滤器。
我的代码和一些背景:
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
的原因是如果我取消 select 一个颜色值并在键中使用一个字符 (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
但是当我 select 2 种或更多颜色相继出现时,或者如果我 select 1 种颜色然后 deselect 它,这会创建一个无限循环。所以问题似乎发生在第二次互动之后:
@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
与 brand
的 belongsTo
。
我现在认为这就是问题所在...
我试过但没用的东西
- 删除
$all_colors
的 @foreach
循环,只将过滤器放在普通 HTML 中(检查问题是否与循环有关)
- 将
wire:key="brand.{{ $brand->id }}"
添加到 input
元素
- 将
wire:key="brand.{{ $brand->id }}"
添加到 input
元素周围的 div
- 按照评论中的建议使用
wire:model="brand.{{ $brand->id }}"
或 wire:model="brand.{{ $loop->id }}"
(我认为解决了问题)
- 使用
wire:model="brand.b{{ $brand->id }}"
所以有一个唯一的键名
- 删除
array_filter
方法(这似乎不太可能是问题,只是为了测试)
- 使用按钮代替复选框
- 使用
defer
、lazy
and/or 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)
所以事实证明,如果你在这样的 foreach
循环中使用 wire:model
,你必须这样写:wire:model="brand.{{ $brand->id }}"
。在文档中找不到它,所以希望它能帮助这里的其他人。
更新
无限循环由此解决,但现在发生的是数组值被设置为零而不是一旦你select一个复选框从数组中删除然后再次点击它取消select。那么 whereIn
将寻找价值 0
的品牌 ID,这不会 return 结果。
更新 2
循环实际上并没有解决...请参阅原始问题。因为浪费了太多时间和咖啡,所以悬赏这个 $%$£#。
我看到的几个问题:
您对 <div>
元素和 <input>
元素使用了相同的 wire:key。我会保留那些独一无二的。
此外,您在输入元素上使用了相同的 name
。这将导致复选框出现问题,因为传递给后端的值具有相同的名称,我相信 livewire 将尝试基于该名称进行同步。将 input
中的 name
字段更改为唯一的可能就是答案。
已回答 GitHub 问题,复制到此处以供其他人查找。
问题是形态问题。
触发错误和循环的代码是标题行和产品行中的 wire:loading。
原因是,当您select两种或多种颜色时,没有显示结果。然后会发生什么是你从显示 heading/products/total 切换到显示空状态。
但是 morphdom 默认情况下并不知道它应该删除旧的 div 并添加新的。相反,它试图将旧的第一个 div“变形”为新的。这意味着 wire:loading 听众在不应该注册的时候仍然注册了。因此,为什么会出现错误和循环。
虽然这是一个简单的修复。您需要向定义它们的 div 添加线键,以便 morphdom 知道它们实际上已经完全改变,并删除旧的并添加新的。
请查看下面的差异屏幕截图,了解我为使其正常工作所做的工作。我为这个文件中的所有顶级 div 添加了一个线键。
建议每当使用这样的条件时,将 wire:keys 添加到条件内第一层的任何元素,以便 morphdom 知道发生了变化。这与 VueJS 存在的问题相同,循环内需要键。
我有一个 Livewire 组件,它是一个产品过滤器。查询都工作正常,但有时它会创建一个无限循环的请求。
您可以在下面的 GIF 中看到发生的情况,它是 Laravel 调试栏的捕获。我正在单击一些过滤器,然后突然进入此请求循环。
我专门在视图中的过滤器上使用了 wire:loading.attr="disabled"
,因此当请求仍在处理时,有人无法 select 过滤器。
我的代码和一些背景:
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
的原因是如果我取消 select 一个颜色值并在键中使用一个字符 (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
但是当我 select 2 种或更多颜色相继出现时,或者如果我 select 1 种颜色然后 deselect 它,这会创建一个无限循环。所以问题似乎发生在第二次互动之后:
@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
与 brand
的 belongsTo
。
我现在认为这就是问题所在...
我试过但没用的东西
- 删除
$all_colors
的@foreach
循环,只将过滤器放在普通 HTML 中(检查问题是否与循环有关) - 将
wire:key="brand.{{ $brand->id }}"
添加到input
元素 - 将
wire:key="brand.{{ $brand->id }}"
添加到input
元素周围的div
- 按照评论中的建议使用
wire:model="brand.{{ $brand->id }}"
或wire:model="brand.{{ $loop->id }}"
(我认为解决了问题) - 使用
wire:model="brand.b{{ $brand->id }}"
所以有一个唯一的键名 - 删除
array_filter
方法(这似乎不太可能是问题,只是为了测试) - 使用按钮代替复选框
- 使用
defer
、lazy
and/ordebounce
- 请专家尝试修复它...
控制台错误
最后,我在控制台中收到此错误仅当无限循环发生时所以这很可能是原因或结果。
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)
所以事实证明,如果你在这样的 foreach
循环中使用 wire:model
,你必须这样写:wire:model="brand.{{ $brand->id }}"
。在文档中找不到它,所以希望它能帮助这里的其他人。
更新
无限循环由此解决,但现在发生的是数组值被设置为零而不是一旦你select一个复选框从数组中删除然后再次点击它取消select。那么 whereIn
将寻找价值 0
的品牌 ID,这不会 return 结果。
更新 2
循环实际上并没有解决...请参阅原始问题。因为浪费了太多时间和咖啡,所以悬赏这个 $%$£#。
我看到的几个问题:
您对 <div>
元素和 <input>
元素使用了相同的 wire:key。我会保留那些独一无二的。
此外,您在输入元素上使用了相同的 name
。这将导致复选框出现问题,因为传递给后端的值具有相同的名称,我相信 livewire 将尝试基于该名称进行同步。将 input
中的 name
字段更改为唯一的可能就是答案。
已回答 GitHub 问题,复制到此处以供其他人查找。
问题是形态问题。
触发错误和循环的代码是标题行和产品行中的 wire:loading。
原因是,当您select两种或多种颜色时,没有显示结果。然后会发生什么是你从显示 heading/products/total 切换到显示空状态。
但是 morphdom 默认情况下并不知道它应该删除旧的 div 并添加新的。相反,它试图将旧的第一个 div“变形”为新的。这意味着 wire:loading 听众在不应该注册的时候仍然注册了。因此,为什么会出现错误和循环。
虽然这是一个简单的修复。您需要向定义它们的 div 添加线键,以便 morphdom 知道它们实际上已经完全改变,并删除旧的并添加新的。
请查看下面的差异屏幕截图,了解我为使其正常工作所做的工作。我为这个文件中的所有顶级 div 添加了一个线键。
建议每当使用这样的条件时,将 wire:keys 添加到条件内第一层的任何元素,以便 morphdom 知道发生了变化。这与 VueJS 存在的问题相同,循环内需要键。