如何知道在 Blazor WASM 中点击了哪个元素

How to know which element was clicked in Blazor WASM

我正在尝试用 16 张“卡片”(只是一个包含 8 对整数的列表)制作一个记忆匹配游戏。

所以我现在要做的是找出我点击了哪张卡片。

我第一个解决这个问题的想法是为每张卡片添加一个 id 属性,但我不知道如何将 id 值传递到 onclick 事件中调用的方法中。

所以,我试着给每张卡片一个索引,然后将该索引号传递给 onclick 事件中的方法。

然而这是行不通的,每次我点击一张卡片,它都会 returns 一个每次增加 16 的数字: screenshot 1

当我点击卡片调试我的代码时,它似乎再次 运行 foreach 循环。为什么?创建页面时,foreachloop 应该只 运行 一次,而不是每次单击卡片时。 screenshot 2

另外,为什么调试器不能在屏幕截图 2 中显示 localIndex 的值?

异常消息:抛出 'Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.ProtocolException' 类型的 localIndex 异常。 (无法评价)

@page "/"

<PageTitle>Index</PageTitle>

<h1>Memory Game!</h1>

<div class="card-grid">

    @foreach (var card in cards)
    {
        int localIndex = cardIndex;
        <div class="mem-card" @onclick="() => ClickCard(localIndex)">@card</div>
        cardIndex++;
    }

</div>

@code{

    List<int> cards = new List<int>();

    int cardIndex;

    protected override void OnInitialized()
    {
        for (var i = 1; i <= 8; i++)
        {
            cards.Add(i);
            cards.Add(i);
        }

        var rng = new Random();
        cards = cards.OrderBy(a => rng.Next()).ToList();
    }


    void ClickCard(int cardIndex)
    {
        Console.WriteLine(cardIndex);
    }
}

您可以这样获取索引:

@foreach (var card in cards.Select((value, index) => (value, index))
{
    <div class="mem-card" @onclick="() => ClickCard(card.index)">@card.value</div>
}

一般来说,Blazor中的Component Model是这样工作的:每当组件的状态发生变化或修改时,需要调用StateHasChanged方法通知Blazor组件状态发生了变化,组件应该re-render 以反映所做的更改。

Blazor 中的

UI 事件(例如 click)不需要手动添加对 StateHasChanged 方法的调用。 StateHasChanged 方法由框架自动调用。此行为基于 UI 事件从本质上改变组件状态的假设。

在你上面的代码中,当你点击一张卡片时,一个UI click事件被触发,你的组件是re-rendered。因此,如果您在组件初始呈现后立即单击按钮,则会发生 re-rendering,变量 cardIndex 的值现在为 16,而下一次单击会使它变为 32,并且等等。

综上所述,每点击一张卡片,组件re-rendered,变量cardIndex的值增加

Re-rendering 是设计使然...

您可以通过重置 cardIndex 变量等来解决这个问题。重要的是从中获取的是 Blazor 的 re-rendering 功能,然后找出正确的方法使用它。

如何做的初步建议...

我建议为您的游戏定义两个组件。一个 parent 和一个 child。 child 组件应包含呈现卡片的代码。还应该 覆盖基础组件的 ShouldRender returns false。它告诉 Blazor 不要渲染组件,初始渲染除外。

@code {
    private bool shouldRender = false;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }
   
} 

根据您想要实现的设计,您可以通过多种方式实现您的游戏。

更新 1:

以上说明了您的代码存在什么问题,模型组件和渲染的工作原理。建议的通过覆盖 ShouldRender 方法来防止 re-rendering 的方法在这里用处不大,因为您的组件必须一次又一次地 re-render。因此,更好的解决方案是使用 @key 指令,如下所示:

@foreach (var card in cards)
{
   <div @key="card" class="mem-card" @onclick="() => 
                 ClickCard(card)">@card</div>
 
}

使用@key 指令是控制哪个元素的更好方法 应该 re-rendered。请注意,在覆盖 ShouldRender 方法防止 re-rendering 完整组件,使用 @key 指令仅启用 re-rendering 的元素 价值已经改变。使用@key 指令是一个好习惯 使用集合时。

更新 2:

我不熟悉游戏,也不知道你打算如何设计你的游戏,所以你的建议只是基于猜测。我将在此处为您提供初始代码,如果您喜欢,建议您对其进行处理……随着时间的推移,我将添加更多内容。请不要犹豫,问我任何问题...

CardGame.razor

<div>@selectedCard.Value</div>

<div class="wrapper">
    @foreach (var card in cards2)
    {
       <div @key="card" @onclick="() => ClickCard(card)" 
       class="box">@card.Value</div>
    }
</div>


@code {

        private List<Card> cards;
        private Card selectedCard = new Card();
        private IEnumerable<Card> cards2;
        private List<int> list;
        protected override void OnInitialized()
        {
            Shuffler shuffler = new Shuffler();
            list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
            shuffler.Shuffle(list);


            cards = Enumerable.Range(1, 8).Select(i => new Card { ID = i, Value = i, Order = list[i - 1] }).ToList();
            cards2 = cards.Concat(Enumerable.Range(1, 8).Select(i => new Card { ID = i + 8, Value = i, Order = list[i + 7] }).ToList());

           cards2 = cards2.OrderBy(c => c.Order);
            
        }

         private void ClickCard(Card card)
         {
            selectedCard = card;
         }

        public class Card
        {
            public int ID { get; set; }
           // public string ImageUrl { get; set; }
           // The Value property should contain the number 
           //  displayed to the user: [1...8 and 1...8][1]  
           // As is shown in an image you provided.
            public int Value { get; set; }
            public int Order { get; set; }
        }
        
        // Utility class to produce unique random number. Note:
        // The random number are going to be stored in Card.Order 
        // and they are going to represent the location or box 
        // in a grid of 4X4 cells   
        public class Shuffler
        {
            private Random rnd;

            public Shuffler()
            {
                rnd = new Random();
            }

           
            public void Shuffle<T>(IList<T> array)
            {
                for (int n = array.Count; n > 1;)
                {
                    int k = rnd.Next(n);
                    --n;
                    T temp = array[n];
                    array[n] = array[k];
                    array[k] = temp;
                }
            }


        }
    }

CardGame.razor.css

    body {
    margin: 40px;
}

.box {
    background-color: #444;
    color: #fff;
    border-radius: 4px;
    padding: 20px;
    font-size: 150%;
   
}

    .box:nth-child(even) {
        background-color: #ccc;
        color: #000;
    }

.wrapper {
    width: 600px;
    display: grid;
    grid-gap: 5px;
    grid-template-columns: repeat(6, 100px);
    grid-template-rows: 100px 100px 100px 100px;
    grid-auto-flow: column;
   
}

Index.razor

<CardGame></CardGame>

复制并测试...记住这只是开始。

您需要比较卡片值而不是它们在列表中的索引。我还建议为每个值创建两张卡片。这将有助于了解是否单击同一张卡片两次。它还可以帮助渲染引擎知道是否有任何更新,因为您可以使用@key。如果相同的值被使用两次,则不能使用@key。

@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.
<div class="card-grid">

    @foreach (var card in cards)
    {
        <div @key=@card class="mem-card" @onclick="() => ClickCard(card)">@card.Value</div>
    }

</div>
@code {
    List<Card> cards = new();

    protected override void OnInitialized()
    {
        var allCardValues = Enum.GetValues<CardValues>();

        foreach (var cardValue in allCardValues)
        {
            cards.Add(new Card(cardValue));
            cards.Add(new Card(cardValue));
        }

        Random rng = new();
        cards = cards.OrderBy(a => rng.Next()).ToList();
    }

    public enum CardValues { Ace, Two, Three, Four, Five, Six, Seven, Eight }

    public class Card
    {
        public Card(CardValues value) => Value = value;
        public CardValues Value { get; }
    }

    void ClickCard(Card card)
    {
        Console.WriteLine(card.Value);
    }
}