AlpineJS x-for 模板

AlpineJS x-for templates

我在复杂的应用程序中遇到 AlpineJS 问题,我发现很难在简化的示例下复制。这很可能意味着它是 Alpine 中的一个错误,但无论如何我都会在这里寻求帮助。我试图将下面的代码缩减为仅包含解释问题所必需的基本要素,这样做可能会导致一些拼写错误。因此,如有任何与问题本身无关的错误,请提前原谅。

我正在使用 Livewire 在我的 PHP classes 和我的 AlpineJS 前端之间同步数据。 PHP class 中相关的两个变量是:

public $colOrder;        // users are able to "re-order" columns on their table-view.  This preference is saved into their profile and stored in this variable as a 1D array of the column-IDs
public $datasourceData;  // contains a 2D data that is pulled from a database with: Model->get()->toArray(); [0 => ['col1'=>'data1,1', 'col2'=>'data1,2'], 1 => ['col1'=>'data2,1', 'col2'=>'data2,2']];

然后将这些数组与 Alpine 变量纠缠在一起,然后从这些数据数组生成模板,如下所示。表面上看,这个模板工作正常:

<div x-data="{
    eColOrder: @entangle('colOrder').defer,
    eData:     @entangle('datasourceData').defer
}">
<table class="table" x-cloak>
  <thead>
    <tr>
      <template x-for="(col, ix) in eColOrder" :key="'th-'+ix">
        <th x-text="col"></th>
      </template>
    </tr>
  </thead>
  <tbody>
    <template x-if="eData.length==0">
      <tr>
        <td :colspan="eColOrder.length" style="padding: 1em">No data found</td>
      </tr>
    </template>
    <template x-if="eData.length>0">
      <template x-for="(rec, ix) in eData" :key="'row-'+ix">
        <tr>
          <td class="action"></td>
          <template x-for="(col, pos) in eColOrder" :key="'td-'+ix+'-'+pos">
            <td x-text="rec[col]"></td> <!-- I also tried `eData[ix][col]`, but it produced errors in the browser console, even though the on-screen display was fine -->
          </template>
        </tr>
      </template>
    </template>
  </tbody>
</table>

在此屏幕截图中,您可以看到用户的搜索(在顶行)在下方生成了表格数据网格。快乐的日子。

当用户重新提交不同的搜索时会出现问题。他们通过更新搜索字段并再次按下“搜索”按钮来实现。这将重新提交搜索(通过 Livewire JSON 调用),用新数据刷新 $datasourceData 数组,将自身与 Alpine 中的 eData 变量纠缠在一起,并产生以下结果:

似乎正在发生的事情是,新搜索的结果被正确地提取出来了。但不知为何,Alpine 并没有清除屏幕上最后一组搜索结果。有趣的是,只有 HTML table 的数据级别被损坏(也就是说,<td> 单元格)。请注意,<th> 单元格(正确地)未在新 table.

的右半部分上方复制

我已经调试并检查了从Eloquent模型返回的数据是正确的,纠缠JavaScript变量eData中的数据结构也是正确的.此问题与数据无关,是渲染问题。

我的直觉是这是一个 Alpine 错误,但我还不能证明这一点。


我的问题到此为止。但是,为了重现该问题并缩小问题原因的范围,我所做的是创建一个简化的 Livewire/Blade/Alpine 页面。严格来说,我无法在那里 直接 复制问题,但是当我在我的代码中故意输入一个“错误”时,我确实(意外地)设法复制了一个类似的输出。

采用以下 PHP/Livewire 组件:

<?php

namespace App\Business\Tbd;

use Livewire\Component;

class StartLw extends Component
{
    public  array $data = [];
    public  array $headings = [];
    public  int $count = 0;

    public function mount() {
        for ($i=1; $i <= 6; $i++) {
            $this->headings[] = "col{$i}";
        }
        $this->data = [];
    }

    public function formSubmit() {
        $src = 1;
        $this->data = [];
        for ($i=0; $i < 10; $i++) {
            $this->data[$i] = [];
            for ($y=1; $y <= 6; $y++) {
                $this->data[$i]["col{$y}"] = "source {$src} ({$i},{$y})";
            }
        }
        $this->count++;
    }

    public function relatedToButSeparateFromForm() {
        $src = 2;
        $this->data = [];
        for ($i=0; $i < 4; $i++) {
            $this->data[$i] = [];
            for ($y=1; $y <= 6; $y++) {
                $this->data[$i]["col{$y}"] = "source {$src} ({$i},{$y})";
            }
        }
        $this->count++;
    }

    public function render()
    {
        return view('components.tbd.lw-start-lw')
            ->layout('layouts.tbd.lw');
    }
}

并且此缩减 HTML 以呈现页面:

<div class="container" x-data="{
    eData: @entangle('data').defer,
    eHeadings: @entangle('headings').defer
}">

<div class="row">
    <div class="col"><p>{{ $count }}</p></div>
</div>

<div class="row">
    <div class="col">
        <form method="post" wire:submit.prevent="formSubmit">
            <p>
            <button type="submit">Load data source 1</button>&nbsp;
            <button type="button" wire:click="relatedToButSeparateFromForm">Load data source 2</button>
            </p>
        </form>
    </div>
</div>

<div class="row">
    <div class="col">
        <table>
            <thead>
                <tr>
                <template x-for="hd in eHeadings">
                    <th x-text="hd" style="padding: 0.5em; background-color:rgb(220,220,230); border: 1px solid rgb(210,210,230)"></th>
                </template>
                </tr>
            </thead>
            <tbody>
                <template x-for="(row, ix) in eData" :key="ix">
                    <tr>
                    <template x-for="(col, pos) in eHeadings" :key="'td-'+ix+'-'+pos">
                        <td x-text="row[col]" :class="id" style="padding: 0.5em; background-color:rgb(240,240,255); border: 1px solid rgb(210,210,230)"></td>
                    </template>
                    </tr>
                </template>
            </tbody>
        </table>
    </div>
</div>
</div>

注意故意的错误!在 <td> 元素上,:class="id" 实际上应该是 :class="col"。现在,如果我排除错误,该页面将按我预期的方式运行。但是随着错误重新引入代码(连同浏览器控制台中的一堆错误消息说:Uncaught ReferenceError: id is not defined),在来回切换两个按钮后,我得到了这个:

我想你会同意,这张照片令人毛骨悚然地想起了我在现实世界应用程序中遇到的情况(除了在现实世界应用程序中,我最终没有出现任何错误浏览器的控制台)。

这让我坚信在 Alpine 引擎的某处触发了一个静默错误,它触发了相同的最终结果。我也会去他们的 GitHub 支持页面上记录这个,但我一直发现 Stack 社区在过去也非常有用。我希望有人能够帮助验证我没有遗漏任何明显的东西!

在 Alpine 错误报告页面上发布了问题,并得到了我想要的回复。参见 >> https://github.com/alpinejs/alpine/discussions/2523#discussioncomment-1860670

显然,这根本不是阿尔卑斯山的问题。问题是 Livewire 踩到了 Alpine 的脚趾。 Livewire“监视”DOM 的更新,似乎它随后无法发布(或清理,或任何正确的术语)DOM 的某些小节,因为 Alpine 使用新的数据负载。这解释了为什么 DOM 的早期版本比他们需要的时间更长。

解决方案是通过使用 wire:ignore 指令强制 Livewire 不监视 DOM 的差异。这可以放在 <table> 本身或其任何父元素上。在我的例子中,我把它放在立即封装 <div>:

<div class="whoopsie" wire:ignore>
    <table>
        <!-- etc -->
        <tbody>
            <template x-for="(col, pos) in eColOrder" :key="'td-'+ix+'-'+pos">
                <td x-data="row[col]"></td>
            </template>
        </tbody>
        <!-- etc -->
    </table>
</div>