Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Примечание.
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
В этой статье объясняется, как использовать @key атрибут директивы для сохранения связей элементов, компонентов и моделей при отрисовке и последующих изменениях элементов или компонентов.
Использование атрибута @key директивы
При отрисовке списка элементов или компонентов и элементов, которые впоследствии изменяются, необходимо решить, Blazor какие из предыдущих элементов или компонентов сохраняются и как объекты модели должны сопоставляться с ними. Как правило, этот процесс является автоматическим и достаточным для общей отрисовки, но часто возникают случаи, когда требуется управление процессом с помощью атрибута @key директивы.
Рассмотрим следующий пример, демонстрирующий проблему сопоставления коллекций, которая решена с помощью @key.
Для следующих компонентов:
- Компонент
Detailsполучает данные (Data) от родительского компонента, который отображается в элементе<input>. Пользователь может установить фокус на любой заданный отображаемый элемент<input>страницы при выборе одного из элементов<input>. - Родительский компонент создает список объектов person для отображения с помощью
Detailsкомпонента. Каждые три секунды в коллекцию добавляется новый пользователь.
В этой демонстрации можно выполнить следующие действия:
- Выбрать
<input>из нескольких отрисованных компонентовDetails. - Изучить поведение фокуса страницы по мере автоматического роста коллекции пользователей.
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; }
}
В следующем родительском компоненте каждая итерация добавления пользователя OnTimerCallback приводит к Blazor перестроению всей коллекции. Фокус страницы остается на одном и том же положении указателя элементов <input>, поэтому при каждом добавлении пользователя фокус сдвигается. Сдвиг фокуса с выбранного пользователем элемента нежелателен. После демонстрации нежелательного поведения с помощью следующего компонента атрибут директивы @key используется для повышения удобства работы пользователя.
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; }
}
}
Содержимое коллекции people изменяется при вставке, удалении или повторном упорядочении записей. Повторная отрисовка может привести к появлению видимых различий в поведении. Например, каждый раз, когда пользователь вставляется в people коллекцию, фокус пользователя теряется.
Процесс сопоставления элементов или компонентов с коллекцией можно контролировать с помощью атрибута @key директивы. Использование @key гарантирует сохранение элементов или компонентов на основе значения ключа. Если фокус компонента Details в предыдущем примере установлен на элемент person, Blazor игнорирует повторную отрисовку компонентов Details, которые не изменились.
Чтобы изменить родительский компонент, чтобы использовать @key атрибут директивы с people коллекцией, обновите <Details> элемент следующим образом:
<Details @key="person" Data="@person.Data" />
При изменении коллекции people связь между экземплярами Details и экземплярами person сохраняется. При вставке Person в начало коллекции один новый экземпляр Details вставляется на соответствующую позицию. Другие экземпляры остаются без изменений. Таким образом, по мере добавления пользователей в коллекцию установленный пользователем фокус не теряется.
Другие обновления коллекции при использовании атрибута @key директивы ведут себя точно так же:
- Если экземпляр удаляется из коллекции, то из пользовательского интерфейса удаляется только соответствующий экземпляр компонента. Другие экземпляры остаются без изменений.
- При переупорядочении записей коллекции соответствующие экземпляры компонентов сохраняются и переупорядочиваются в пользовательском интерфейсе.
Внимание
Ключи являются локальными для каждого компонента или элемента контейнера. Ключи не сравниваются глобально по всему документу.
Когда следует использовать @key
Как правило, @key имеет смысл использовать при отрисовке списка (например, в блоке foreach) и при наличии подходящего значения для определения @key.
Если объект не изменяется, @key можно также использовать для сохранения поддерева элемента или компонента, как показано в следующих примерах.
Пример 1:
<li @key="person">
<input value="@person.Data" />
</li>
Пример 2:
<div @key="person">
@* other HTML elements *@
</div>
При изменении экземпляра person атрибут @key директивы заставляет Blazor выполнить следующие действия:
- Полностью отменить
<li>или<div>, а также их потомков. - Перестроить поддерево в пользовательском интерфейсе с помощью новых элементов и компонентов.
Это позволяет гарантировать сохранение состояния пользовательского интерфейса при изменении коллекции в поддереве.
Область действия @key
Областью действия директивы атрибута @key являются другие дочерние элементы в ее родителе.
Рассмотрим следующий пример. Ключи first и second сравниваются друг с другом в общей области внешнего элемента <div>:
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
В следующем примере ключи first и second находятся каждый в своей области, которые не связаны и никак не влияют друг на друга. Область каждого @key относится только к его собственному родительскому элементу <div>, а не к общим родительским элементам <div>:
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
В следующих примерах для упомянутого ранее компонента Details выводятся данные person в области @key, а также показаны основные варианты использования @key:
<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>
В следующих примерах областью действия @key является только элемент <div> или <li>, охватывающий соответствующий экземпляр компонента Details. В этом случае данные person для каждого элемента коллекции peopleне соответствуют по ключу экземплярам person в выводимых компонентах Details. Избегайте при использовании @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>
Когда не следует использовать @key
Отрисовка с использованием @key подразумевает определенное снижение производительности. Это снижение производительности незначительно, но указывать @key следует только в том случае, если сохранение элементов или компонентов выгодно для приложения.
Даже если @key не используется, Blazor сохраняет экземпляры дочерних элементов и компонентов в максимально возможной степени. Единственным преимуществом использования @key является контроль над тем, как экземпляры модели сопоставляются с сохраненными экземплярами компонентов, вместо выбора сопоставления с помощью Blazor.
Используемые значения для @key
Как правило, для @key имеет смысл указать одно из следующих значений:
- Экземпляры объектов моделей. Например, в предыдущем примере использовался экземпляр
Person(person). Это гарантирует сохранение на основе равенства ссылок на объекты. - Уникальные идентификаторы. Например, уникальные идентификаторы могут основываться на значениях первичного ключа типа
int,stringилиGuid.
Убедитесь, что значения, используемые для @key, не конфликтуют. Если в одном родительском элементе обнаруживаются конфликтующие значения, Blazor выдает исключение, поскольку не может детерминированно сопоставлять старые элементы или компоненты с новыми. Используйте только уникальные значения, такие как экземпляры объекта или значения первичного ключа.