简体   繁体   English

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

[英]Blazor/razor onclick event with index parameter

I have the below code but the index parameter that is passed when I click the <tr> element is always 9.我有下面的代码,但是当我单击<tr>元素时传递的索引参数始终是 9。

That is becuase I have 9 rows in the table that is passed to the component as data.那是因为我在表中有 9 行作为数据传递给组件。 So looks like the index is always the value of variable 'i' which was last set... in this case the value of i after the last row in foreach loop is 9 so i am getting the index parameter as 9 on clicking all the rows in the table...所以看起来索引始终是最后设置的变量“i”的值......在这种情况下,foreach 循环中最后一行之后的 i 的值是 9,所以我在单击所有时将索引参数设置为 9表中的行...

What is the issue in my code which is not setting the i value for each row onclick.我的代码中没有为每行 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);
    }
}

General一般的

Actually your problem is about lambda that captures local variable.实际上,您的问题是关于捕获局部变量的 lambda 。 See the following simulation with a console application for the sake of simplicity.为简单起见,请参阅以下使用控制台应用程序的模拟。

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);
}

It will output 3, 3, 3 thrice rather than 0, 1, 2 .它将 output 3, 3, 3三次而不是0, 1, 2

Blazor Blazor

Quoted from the official documentation about EventCallback :引用自有关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}.";
    }
}

Do not use a loop variable directly in a lambda expression, such as i in the preceding for loop example.不要在 lambda 表达式中直接使用循环变量,例如前面 for 循环示例中的i Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas.否则,所有 lambda 表达式都使用相同的变量,这会导致在所有 lambda 中使用相同的值。 Always capture the variable's value in a local variable and then use it.始终在局部变量中捕获变量的值,然后使用它。 In the preceding example, the loop variable i is assigned to buttonNumber .在前面的示例中,循环变量 i 被分配给buttonNumber

So you need to make a local copy for i as follows.所以你需要为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>
}

This happens because the value of i isn't rendered to the page (as it would have been in MVC/Razor Pages), it's just evaluated when you trigger the event.发生这种情况是因为i的值没有呈现到页面(就像在 MVC/Razor 页面中一样),它只是在您触发事件时进行评估。 You won't trigger the event until the page has rendered, and so by that point the loop will have completed, so the value for i will always be the value at the end of the loop.在页面呈现之前,您不会触发事件,因此到那时循环将完成,因此i的值将始终是循环结束时的值。

There are a couple of ways to deal with this, either use a foreach loop instead if that's suitable (which is what most of the Blazor documentation examples do), or declare a local variable inside the loop:有几种方法可以解决这个问题,或者在合适的情况下使用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
}

There's a good discussion of this in the Blazor Docs repo here , which explains the problem as follows: Blazor Docs repo 中对此进行了很好的讨论,该问题解释如下:

Problem is typically seen in event handlers and binding expressions.问题通常出现在事件处理程序和绑定表达式中。 We should explain that in for loop we have only one iteration variable and in foreach we have a new variable for every iteration.我们应该解释一下,在for循环中,我们只有一个迭代变量,而在foreach中,每次迭代都有一个新变量。 We should explain that HTML content is rendered when for / foreach loop is executed, but event handlers are called later.我们应该解释一下,HTML 内容是在执行for / foreach循环时呈现的,但稍后会调用事件处理程序。 Here is an example code to demonstrate one wrong and two good solutions.这是一个示例代码来演示一个错误和两个好的解决方案。

This is all particularly confusing if you're coming to Blazor from an MVC/Razor Page background, where using for is the normal behaviour.如果您从 MVC/Razor 页面背景进入 Blazor,这一切都特别令人困惑,其中使用for是正常行为。 The difference is that in MVC the value of i is actually written to the html on the page, and so would be different for each row of the table in your example.不同之处在于,在 MVC 中, i的值实际上是写入页面上的 html 的,因此对于您的示例中的表格的每一行来说都是不同的。

As per the issue linked above and @Fat Man No Neck's answer, the root cause of this is down to differences in the behaviour of for and foreach loops with lambda expressions.根据上面链接的问题和@Fat Man No Neck 的回答,其根本原因在于forforeach循环与 lambda 表达式的行为差异。 It's not a Blazor bug, it's just how C# works.这不是 Blazor 错误,而是 C# 的工作原理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM