如何知道在 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);
}
}
我正在尝试用 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);
}
}