AlpineJS x-for 模板

Posted

技术标签:

【中文标题】AlpineJS x-for 模板【英文标题】:AlpineJS x-for templates 【发布时间】:2022-01-24 00:34:50 【问题描述】:

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

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

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 表的数据级别被损坏(也就是说,&lt;td&gt; 单元格)。请注意,&lt;th&gt; 单元格没有(正确地)在新表格的右半部分上方重复。

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

我的直觉是这是一个阿尔卑斯山的虫子,但我还不能证明这一点。


我的问题到此为止。但是,为了重现问题并缩小问题的原因,我所做的是创建一个简化的 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>

注意故意的错误!在&lt;td&gt; 元素上,:class="id" 应该说是:class="col"。现在,如果我删除错误,页面会按我的预期工作。但是在代码中重新引入了错误(连同浏览器控制台中的一堆错误消息说:Uncaught ReferenceError: id is not defined),在来回切换两个按钮之后,我得到了这个:

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

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

【问题讨论】:

【参考方案1】:

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

显然,这根本不是阿尔卑斯山的问题。问题是 Livewire 正在踩着 Alpine 的脚趾。 Livewire “监视” DOM 的更新,然后似乎它未能释放(或清理,或任何正确术语)DOM 的某些子部分,因为 Alpine 用新的数据负载刷新它。这就解释了为什么早期的 DOM 化身比它们需要的时间更长。

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

<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>

【讨论】:

以上是关于AlpineJS x-for 模板的主要内容,如果未能解决你的问题,请参考以下文章

[Mise] Iterate through data with the `x-for` attribute in Alpine JS

使用 Alpine JS 切换元素与类?

如何通过 Webpacker 使用 Rails 6.1 安装 Alpine JS 3

用于动态选择菜单的 AlpineJS

Alpine JS 切换输入值

如何通过使用alpine js单击一个复选框来选中和取消选中所有复选框