带有索引参数的 Blazor/razor onclick 事件

问题描述 投票:0回答:3

我有以下代码,但当我单击

<tr>
元素时传递的索引参数始终为 9。

那是因为表中有 9 行作为数据传递给组件。 所以看起来索引总是最后设置的变量“i”的值...在这种情况下,foreach循环中最后一行之后的i的值是9,所以我在单击所有按钮时得到索引参数为9表中的行...

我的代码中有什么问题没有为每行 onclick 设置

i
值。

    <table border="1">

        @for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
        {
            <tr @onclick="(() => RowSelect(i))">
                @foreach (ModelColumn col in ListData.ListColumns)
                {
                    <td>@ListData.DataView.Table.Rows[i][col.Name]</td>
                }
            </tr>
        }

    </table>
@code {
 
    private async Task  RowSelect(int rowIndex)
    {
        await ListRowSelected.InvokeAsync(rowIndex);
    }
}
c# razor-pages blazor-server-side
3个回答
20
投票

一般

实际上你的问题是关于捕获局部变量的 lambda 。为了简单起见,请参阅以下使用控制台应用程序的模拟。

class Program
{
    static void Main(string[] args)
    {
        Action[] acts = new Action[3];

        for (int i = 0; i < 3; i++)
            acts[i] = (() => Job(i));

        foreach (var act in acts) act?.Invoke();
    }

    static void Job(int i) => Console.WriteLine(i);
}

它将输出三次

3, 3, 3
而不是
0, 1, 2

开拓者

引用官方文档关于EventCallback

<h2>@message</h2>

@for (var i = 1; i < 4; i++)
{
    var buttonNumber = i;

    <button class="btn btn-primary"
            @onclick="@(e => UpdateHeading(e, buttonNumber))">
        Button #@i
    </button>
}

@code {
    private string message = "Select a button to learn its position.";

    private void UpdateHeading(MouseEventArgs e, int buttonNumber)
    {
        message = $"You selected Button #{buttonNumber} at " +
            $"mouse position: {e.ClientX} X {e.ClientY}.";
    }
}

请勿直接在 lambda 表达式中使用循环变量,例如前面的 for 循环示例中的

i
。否则,所有 lambda 表达式都会使用相同的变量,这会导致所有 lambda 中使用相同的值。始终捕获局部变量中的变量值,然后使用它。在前面的示例中,循环变量 i 被分配给
buttonNumber

因此您需要为

i
制作一份本地副本,如下所示。

@for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
   int buffer=i;
    <tr @onclick="(() => RowSelect(buffer))">
        @foreach (ModelColumn col in ListData.ListColumns)
        {
            <td>@ListData.DataView.Table.Rows[i][col.Name]</td>
        }
    </tr>
}

5
投票

发生这种情况是因为

i
的值未呈现到页面(就像在 MVC/Razor 页面中一样),它只是在触发事件时进行评估。 在页面呈现之前,您不会触发该事件,因此到那时循环将完成,因此
i
的值将始终是循环结束时的值。

有几种方法可以处理这个问题,如果合适的话可以使用

foreach
循环(这是大多数 Blazor 文档示例所做的),或者在循环内声明一个局部变量:

@for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
   int local_i=i;

    // Now use local_i instead of i in the code inside the for loop
}

这里的 Blazor Docs 存储库对此进行了很好的讨论,它解释了该问题如下:

问题通常出现在事件处理程序和绑定表达式中。 我们应该解释一下,在

for
循环中我们只有一个迭代变量 在
foreach
中,我们为每次迭代都有一个新变量。我们应该 解释当
for
/
foreach
循环出现时,会渲染 HTML 内容 执行,但稍后调用事件处理程序。这是一个示例代码 展示一个错误的解决方案和两个好的解决方案。

如果您从 MVC/Razor 页面背景进入 Blazor,那么这一切尤其令人困惑,其中使用

for
是正常行为。 不同之处在于,在 MVC 中,
i
的值实际上写入页面上的 html,因此示例中表格的每一行都会有所不同。

根据上面链接的问题和 @Fat Man No Neck 的回答,其根本原因在于

for
foreach
循环与 lambda 表达式的行为差异。 这不是 Blazor bug,这只是 C# 的工作原理。


0
投票

我刚刚通过下面的示例奋力拼搏,我想分享一下。 这个示例使用

foreach
而不是
for
,但我仍然被“触发事件时评估索引值”问题所困扰。 下面的代码按照我想要的方式工作,但我将解释如何将其更改为我最初拥有的方式 - 即代码模式不按预期工作:

@{ int itemIndex = -1; }

<ul>

    @foreach( var mItem in Anchors ) {

        itemIndex++;

        var idx = itemIndex;
        
        <li @onpointerup="() => RunMyRoutine( Anchors[idx].Label )">
        
            <a href="@Anchors[idx].URL">@Anchors[idx].Label</a>
            
        </li>

    }

</ul>

上面的三个地方都有 [idx] ——如果你将它们更改为在 foreach 执行时更改的值,

itemIndex
,你就会遇到问题。 问题是,虽然
<a>
标签是按预期构建的,且 URL 正确且标签正确,但当
onpointerup
事件发生时,发送到例程的值是 always 最后一个值,即最大索引价值。 这就是事件发生时的问题,索引值在事件时评估,并且它是代码循环迭代期间设置的最后一个值。

这个问题可以通过

var idx = itemIndex
行解决。 随着 foreach 的迭代, idx 不断获得正确的值,令人惊奇的是,传递给它的 idx 获得了正确的预期值,该值与代码的标记创建部分期间评估的值相匹配(锚点,在这种情况下)。
我希望这个答案有帮助,我当然欢迎任何人编辑这个答案以提高清晰度。

© www.soinside.com 2019 - 2024. All rights reserved.