Vyhněte se přepisování parametrů v ASP.NET Core Blazor

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Autor: Robert Haken

Tento článek vysvětluje, jak se vyhnout přepsání parametrů v Blazor aplikacích během opětovnéhoenderování.

Přepsané parametry

Architektura Blazor obecně vynucuje bezpečné přiřazování parametrů mezi nadřazenými a podřízenými komponentami:

  • Parametry se neočekávaně nepřepisují.
  • Vedlejší účinky jsou minimální. Například se zabrání dalšímu vykreslování, protože by mohly vznikat nekonečné smyčky vykreslování.

Když se nadřazená komponenta vykreslí znovu, podřízená komponenta přijme nové hodnoty parametrů, které můžou přepsat stávající hodnoty. K náhodnému přepsání hodnot parametrů v podřízené komponentě často dochází při vývoji komponenty s jedním nebo více parametry vázanými na data, když vývojář zapisuje přímo do parametru v podřízené komponentě:

  • Podřízená komponenta se vykreslí s jednou nebo více hodnotami parametrů z nadřazené komponenty.
  • Podřízená komponenta provede zápis přímo do hodnoty parametru.
  • Nadřazená komponenta se vykreslí znovu a přepíše hodnotu parametru podřízené komponenty.

Možnost přepsání hodnot parametrů se týká i přístupových objektů set vlastností podřízené komponenty.

Důležité

Obecně doporučujeme nevytvářet komponenty, které po prvotním vykreslení zapisují přímo do svých vlastních parametrů.

Podívejte se například na následující komponentu ShowMoreExpander, která:

  • Vykreslí název.
  • Zobrazí podřízený obsah při výběru.
  • Umožňuje nastavit počáteční stav pomocí parametru komponenty (InitiallyExpanded).

Následující komponenta ShowMoreExpander ukazuje přepsaný parametr. Dále najdete ukázku upravené komponenty ShowMoreExpander, která ukazuje správný přístup pro tento scénář. Následující příklady můžete umístit do místní ukázkové aplikace, abyste si popsané chování vyzkoušeli.

ShowMoreExpander.razor:

<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @InitiallyExpanded)</h2>
    </div>
    @if (InitiallyExpanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private void ShowMore()
    {
        InitiallyExpanded = true;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @InitiallyExpanded)</h2>
    </div>
    @if (InitiallyExpanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private void ShowMore()
    {
        InitiallyExpanded = true;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @InitiallyExpanded)</h2>
    </div>
    @if (InitiallyExpanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private void ShowMore()
    {
        InitiallyExpanded = true;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @InitiallyExpanded)</h2>
    </div>
    @if (InitiallyExpanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private void ShowMore()
    {
        InitiallyExpanded = true;
    }
}
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @InitiallyExpanded)</h2>
    </div>
    @if (InitiallyExpanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private void ShowMore()
    {
        InitiallyExpanded = true;
    }
}

Komponenta ShowMoreExpander se přidá do následující nadřazené komponenty Expanders, která může volat metodu StateHasChanged:

Expanders.razor:

@page "/expanders"

<PageTitle>Expanders</PageTitle>

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<PageTitle>Expanders</PageTitle>

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<PageTitle>Expanders</PageTitle>

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>
@page "/expanders"

<h1>Expanders Example</h1>

<ShowMoreExpander InitiallyExpanded="false">
    Expander 1 content
</ShowMoreExpander>

<ShowMoreExpander InitiallyExpanded="false" />

<button @onclick="StateHasChanged">Call StateHasChanged</button>

Komponenty se zpočátku ShowMoreExpander chovají nezávisle při nastavení jejich InitiallyExpanded vlastností. Podřízené komponenty si udržují své stavy podle očekávání.

Pokud se metoda StateHasChanged zavolá v nadřazené komponentě, architektura Blazor znovu vykreslí podřízené komponenty, pokud se jejich parametry mohly změnit:

  • V případě skupiny typů parametrů, které Blazor explicitně kontroluje, Blazor znovu vykreslí podřízenou komponentu, pokud zjistí, že se některý z parametrů změnil.
  • V případě typů parametrů, které se nekontrolují, Blazor znovu vykreslí podřízenou komponentu bez ohledu na to, jestli se parametry změnily. Podřízený obsah spadá do této kategorie typů parametrů, protože je typu RenderFragment, což je delegát, který odkazuje na další měnitelné objekty.

Komponenta Expanders:

  • První ShowMoreExpander komponenta nastaví podřízený obsah v potenciálně proměnlivé RenderFragment, takže volání StateHasChanged v nadřazené komponentě automaticky rerenderuje komponentu a potenciálně přepíše hodnotu InitiallyExpanded na jeho počáteční hodnotu false.
  • Druhá komponenta ShowMoreExpander podřízený obsah nenastavuje. Potenciálně měnitelný objekt RenderFragment tak vůbec neexistuje. Při zavolání metody StateHasChanged v nadřazené komponentě se podřízená komponenta nevykreslí automaticky znovu, takže nedojde k přepsání hodnoty InitiallyExpanded komponenty.

Chcete-li zachovat stav v předchozím scénáři, použijte soukromé pole v komponentě ShowMoreExpander k udržování jeho stavu.

Následující upravená komponenta ShowMoreExpander:

  • Přijme hodnotu parametru komponenty InitiallyExpanded z nadřazené komponenty.
  • Přiřadí hodnotu parametru komponenty privátnímu poli (expanded) v události OnInitialized.
  • Pomocí privátního pole udržuje svůj interní stav přepnutí, což ukazuje, jak zabránit přímému zápisu do parametru.

Poznámka:

Rady v této části se vztahují i na podobnou logiku v přístupových objektech set parametrů komponent, která může vést k podobným nežádoucím vedlejším účinkům.

ShowMoreExpander.razor:

<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    protected override void OnInitialized()
    {
        expanded = InitiallyExpanded;
    }

    private void Expand()
    {
        expanded = true;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    protected override void OnInitialized()
    {
        expanded = InitiallyExpanded;
    }

    private void Expand()
    {
        expanded = true;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    protected override void OnInitialized()
    {
        expanded = InitiallyExpanded;
    }

    private void Expand()
    {
        expanded = true;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    protected override void OnInitialized()
    {
        expanded = InitiallyExpanded;
    }

    private void Expand()
    {
        expanded = true;
    }
}
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
    <div class="card-header">
        <h2 class="card-title">Show more (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool InitiallyExpanded { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    protected override void OnInitialized()
    {
        expanded = InitiallyExpanded;
    }

    private void Expand()
    {
        expanded = true;
    }
}

Poznámka:

Revidovaný ShowMoreExpander parametr po inicializaciOnInitialized neodráží změnyInitiallyExpanded. V určitých scénářích může již inicializovaná komponenta přijímat nové hodnoty parametrů. K tomu může dojít například v zobrazení hlavních podrobností, kde se stejná komponenta používá k vykreslení různých zobrazení podrobností nebo když /item/{id} se parametr trasy změní, aby se zobrazila jiná položka.

Zvažte následující ToggleExpander komponentu, která:

  • Umožňuje změnit stav uvnitř i vně.
  • Zpracovává nové hodnoty parametrů i v případě, že je znovu použita stejná instance komponenty.

ToggleExpander.razor:

<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}
<div class="card bg-light mb-3" style="width:30rem">
    <div @onclick="Toggle" class="card-header">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @expanded)</h2>
    </div>
    @if (expanded)
    {
        <div class="card-body">
            <p class="card-text">@ChildContent</p>
        </div>
    }
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public EventCallback<bool> ExpandedChanged { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool expanded;

    protected override void OnParametersSet()
    {
        expanded = Expanded;
    }

    private async void Toggle()
    {
        expanded = !expanded;
        await ExpandedChanged.InvokeAsync(expanded);
    }
}

Komponenta ToggleExpander by měla být použita se @bind-Expanded="{field}" syntaxí vazby, což umožňuje obousměrnou synchronizaci parametru.

ExpandersToggle.razor:

@page "/expanders-toggle"

<PageTitle>Expanders Toggle</PageTitle>

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<PageTitle>Expanders Toggle</PageTitle>

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<PageTitle>Expanders Toggle</PageTitle>

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}
@page "/expanders-toggle"

<h1>Expanders Toggle</h1>

<ToggleExpander @bind-Expanded="expanded">
    Expander content
</ToggleExpander>

<button @onclick="Toggle">Toggle</button>

<button @onclick="StateHasChanged">Call StateHasChanged</button>

@code {
    private bool expanded;

    private void Toggle()
    {
        expanded = !expanded;
    }
}

Další informace o vazbě nadřazenosti a podřízenosti najdete v následujících zdrojích informací:

Další informace o detekci změn, včetně informací o přesných typech, které Blazor kontroluje, najdete v tématu ASP.NET vykreslování komponent CoreRazor.