Delen via


Element-, onderdeel- en modelrelaties behouden in ASP.NET Core Blazor

Opmerking

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 10-versie van dit artikel voor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.

In dit artikel wordt uitgelegd hoe u het @key kenmerk van de richtlijn gebruikt om elementen, componenten en modelrelaties te behouden bij het weergeven en vervolgens wijzigen van de elementen of componenten.

Gebruik van het @key instructiekenmerk

Bij het weergeven van een lijst met elementen of onderdelen en wanneer deze vervolgens worden gewijzigd, moet Blazor bepalen welke van de eerdere elementen of onderdelen behouden blijven en hoe modelobjecten hieraan moeten worden toegewezen. Normaal gesproken is dit proces automatisch en voldoende voor algemene rendering, maar er zijn vaak gevallen waarin het beheren van het proces met behulp van het @key instructiekenmerk is vereist.

Bekijk het volgende voorbeeld waarin een verzamelingstoewijzing probleem gedemonstreerd wordt dat met behulp van @key is opgelost.

Voor de volgende onderdelen:

  • Het Details onderdeel ontvangt gegevens (Data) van het bovenliggende onderdeel, die in een <input> element wordt weergegeven. Elk weergegeven <input> element kan de focus van de pagina van de gebruiker krijgen wanneer deze een van de <input> elementen selecteert.
  • Het oudercomponent maakt een lijst van persoonsobjecten om weer te geven met behulp van het Details component. Elke drie seconden wordt een nieuwe persoon toegevoegd aan de verzameling.

Met deze demonstratie kunt u het volgende doen:

  • Selecteer een <input> van de verschillende gerenderde Details onderdelen.
  • Onderzoek het gedrag van de focus van de pagina naarmate de verzameling personen automatisch groeit.

Details.razor:

<input value="@Data" />

@code {
    [Parameter]
    public string? Data { get; set; }
}
<input value="@Data" />

@code {
    [Parameter]
    public string? Data { get; set; }
}
<input value="@Data" />

@code {
    [Parameter]
    public string? Data { get; set; }
}
<input value="@Data" />

@code {
    [Parameter]
    public string? Data { get; set; }
}
<input value="@Data" />

@code {
    [Parameter]
    public string Data { get; set; }
}
<input value="@Data" />

@code {
    [Parameter]
    public string Data { get; set; }
}

In het volgende bovenliggende onderdeel leidt elke iteratie van het toevoegen van een persoon in OnTimerCallback ertoe dat Blazor de hele verzameling opnieuw wordt opgebouwd. De focus van de pagina blijft op dezelfde indexpositie van <input> elementen staan, dus de focus wordt steeds verplaatst wanneer een persoon wordt toegevoegd. Het verplaatsen van de focus van wat de gebruiker heeft geselecteerd, is niet wenselijk gedrag. Nadat het slechte gedrag met het volgende onderdeel is gedemonstreerd, wordt het @key directive kenmerk gebruikt om de gebruikerservaring te verbeteren.

People.razor:

@page "/people"
@using System.Timers
@implements IDisposable

<PageTitle>People</PageTitle>

<h1>People Example</h1>

@foreach (var person in people)
{
    <Details Data="@person.Data" />
}

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();

    public class Person
    {
        public string? Data { get; set; }
    }
}

People.razor:

@page "/people"
@using System.Timers
@implements IDisposable

<PageTitle>People</PageTitle>

<h1>People Example</h1>

@foreach (var person in people)
{
    <Details Data="@person.Data" />
}

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();

    public class Person
    {
        public string? Data { get; set; }
    }
}

PeopleExample.razor:

@page "/people-example"
@using System.Timers
@implements IDisposable

@foreach (var person in people)
{
    <Details Data="@person.Data" />
}

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();

    public class Person
    {
        public string? Data { get; set; }
    }
}

PeopleExample.razor:

@page "/people-example"
@using System.Timers
@implements IDisposable

@foreach (var person in people)
{
    <Details Data="@person.Data" />
}

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();

    public class Person
    {
        public string? Data { get; set; }
    }
}

PeopleExample.razor:

@page "/people-example"
@using System.Timers
@implements IDisposable

@foreach (var person in people)
{
    <Details Data="@person.Data" />
}

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();

    public class Person
    {
        public string Data { get; set; }
    }
}

PeopleExample.razor:

@page "/people-example"
@using System.Timers
@implements IDisposable

@foreach (var person in people)
{
    <Details Data="@person.Data" />
}

@code {
    private Timer timer = new Timer(3000);

    public List<Person> people =
        new List<Person>()
        {
            { new Person { Data = "Person 1" } },
            { new Person { Data = "Person 2" } },
            { new Person { Data = "Person 3" } }
        };

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            people.Insert(0,
                new Person
                {
                    Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
                });
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();

    public class Person
    {
        public string Data { get; set; }
    }
}

De inhoud van de people verzameling wordt gewijzigd met ingevoegde, verwijderde of opnieuw geordende items. Rerendering kan leiden tot verschillen in gedrag die zichtbaar zijn. Telkens wanneer iemand in de people verzameling wordt ingevoegd, gaat de focus van de gebruiker verloren.

Het toewijzingsproces van elementen of componenten aan een verzameling kan worden beheerd met het @key richtlijnkenmerk. Het gebruik van @key garandeert het behoud van elementen of onderdelen op basis van de sleutelwaarde. Als de Details component in het vorige voorbeeld is gekoppeld aan het person item, negeert Blazor het opnieuw renderen van Details componenten die niet zijn gewijzigd.

Als u het bovenliggende onderdeel wilt wijzigen om het @key instructiekenmerk met de people verzameling te gebruiken, werkt u het <Details> element bij naar het volgende:

<Details @key="person" Data="@person.Data" />

Wanneer de people verzameling wordt gewijzigd, blijft de koppeling tussen Details instanties en person exemplaren behouden. Wanneer een Person exemplaar aan het begin van de verzameling wordt ingevoegd, wordt er een nieuw Details exemplaar ingevoegd op die overeenkomstige positie. Andere exemplaren blijven ongewijzigd. De focus van de gebruiker gaat daarom niet verloren wanneer personen aan de verzameling worden toegevoegd.

Andere verzamelingsupdates vertonen hetzelfde gedrag wanneer het @key instructiekenmerk wordt gebruikt:

  • Als een exemplaar uit de verzameling wordt verwijderd, wordt alleen het bijbehorende onderdeelexemplaar uit de gebruikersinterface verwijderd. Andere exemplaren blijven ongewijzigd.
  • Als verzamelingsvermeldingen opnieuw worden gerangschikt, blijven de bijbehorende onderdeelexemplaren behouden en opnieuw gerangschikt in de gebruikersinterface.

Belangrijk

Sleutels zijn lokaal voor elk containerelement of elk onderdeel. Sleutels worden niet globaal in het document vergeleken.

Wanneer gebruikt u @key

Normaal gesproken is het zinvol om te gebruiken @key wanneer een lijst wordt weergegeven (bijvoorbeeld in een foreach blok) en er een geschikte waarde bestaat om de @keylijst te definiƫren.

U kunt ook een @key element- of onderdeelsubstructuur behouden wanneer een object niet wordt gewijzigd, zoals in de volgende voorbeelden wordt weergegeven.

Voorbeeld 1:

<li @key="person">
    <input value="@person.Data" />
</li>

Voorbeeld 2:

<div @key="person">
    @* other HTML elements *@
</div>

Als een person instantie wordt gewijzigd, dwingt @key de Blazor attribuutrichtlijn om het volgende uit te voeren:

  • Verwijder volledig <li> of <div> en hun nakomelingen.
  • Bouw de substructuur in de gebruikersinterface opnieuw met nieuwe elementen en onderdelen.

Dit is handig om te garanderen dat er geen UI-status behouden blijft wanneer de verzameling in een substructuur verandert.

Bereik van @key

De @key kenmerkrichtlijn is van toepassing op zijn eigen andere elementen binnen het bovenliggende element.

Bekijk het volgende voorbeeld. De first en second sleutels worden met elkaar vergeleken binnen dezelfde reikwijdte van het buitenste <div> element.

<div>
    <div @key="first">...</div>
    <div @key="second">...</div>
</div>

Het volgende voorbeeld toont first en second sleutels in hun eigen contexten, die niet met elkaar verbonden zijn en geen invloed op elkaar hebben. Elk @key bereik is alleen van toepassing op zijn directe bovenliggende <div> element, niet op andere bovenliggende <div> elementen.

<div>
    <div @key="first">...</div>
</div>
<div>
    <div @key="second">...</div>
</div>

Voor het eerder getoonde Details-onderdeel worden in de volgende voorbeelden person-gegevens binnen hetzelfde bereik weergegeven en worden typische gebruiksvoorbeelden voor @key gedemonstreerd.

<div>
    @foreach (var person in people)
    {
        <Details @key="person" Data="@person.Data" />
    }
</div>
@foreach (var person in people)
{
    <div @key="person">
        <Details Data="@person.Data" />
    </div>
}
<ol>
    @foreach (var person in people)
    {
        <li @key="person">
            <Details Data="@person.Data" />
        </li>
    }
</ol>

De volgende voorbeelden beperken zich tot het @key-gedeelte dat wordt omsloten door het <div>- of <li>-element dat elke Details component omsluit. person Daarom worden gegevens voor elk lid van de people verzameling niet bij elk person exemplaar van de weergegeven Details onderdelen gesleuteld. Vermijd de volgende patronen bij gebruik @key:

@foreach (var person in people)
{
    <div>
        <Details @key="person" Data="@person.Data" />
    </div>
}
<ol>
    @foreach (var person in people)
    {
        <li>
            <Details @key="person" Data="@person.Data" />
        </li>
    }
</ol>

Wanneer niet te gebruiken @key

Er zijn prestatiekosten bij het weergeven met @key. De prestatiekosten zijn niet groot, maar specificeer @key alleen als het handhaven van het element of onderdeel voordelen oplevert voor de app.

Zelfs als @key niet wordt gebruikt, worden de onderliggende elementen en onderdeelexemplaren door Blazor zoveel mogelijk behouden. Het enige voordeel van het gebruik @key is de controle over hoe modelinstanties worden toegewezen aan de bewaarde onderdeelexemplaren in plaats van Blazor de toewijzing te selecteren.

Te gebruiken waarden voor @key

Over het algemeen is het zinvol om een van de volgende waarden op te geven voor @key:

  • Modelobjectexemplaren. Het Person exemplaar (person) werd bijvoorbeeld gebruikt in het eerdere voorbeeld. Dit zorgt voor behoud op basis van objectreferentie-gelijkheid.
  • Unieke ID's. Unieke id's kunnen bijvoorbeeld worden gebaseerd op primaire sleutelwaarden van het type int, stringof Guid.

Zorg ervoor dat waarden die worden gebruikt voor @key niet conflicteren. Als conflicterende waarden worden gedetecteerd binnen hetzelfde bovenliggende element, genereert Blazor een uitzondering omdat oude elementen of onderdelen niet deterministisch kunnen worden gemapt naar nieuwe elementen of onderdelen. Gebruik alleen afzonderlijke waarden, zoals objectexemplaren of primaire-sleutelwaarden.