Blazor table 组件未动态更新
Blazor table component not updating dynamically
我 运行 遇到 server-side Blazor 试图创建自定义 table 组件的问题。 table 行中的数据动态更新和更改,因此这不是问题,但如果我将 header 绑定到 属性,header 将采用之前的值属性.
从 table.razor 开始,我正在设置一个带有默认值的简单下拉列表 <select>
标记。当该值更改时,它应该更新 table header.
上的值
我添加了一个 <code>
标签和一个经典的 HTML table 作为测试,它们都正确地反映了新的 <select>
值。知道为什么自定义组件不一样吗?
Table.razor
@page "/table"
@using BlazorApp1.Data
@inject WeatherForecastService ForecastService
<select @bind="@SelectedListItem">
<option value="test">Test</option>
<option value="test2">Test2</option>
</select>
<code>@SelectedListItem</code>
<table>
<thead>
<tr>
<th>@SelectedListItem</th>
</tr>
<tbody>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
<tr>
<td>5</td>
</tr>
</tbody>
</thead>
</table>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<BlazorApp1.Components.DataTable Items=@forecasts TRowItem="WeatherForecast">
<BlazorApp1.Components.Column CustomTitle="@SelectedListItem" TRowItem="WeatherForecast"></BlazorApp1.Components.Column>
</BlazorApp1.Components.DataTable>
}
@code{
public string SelectedListItem { get; set; }
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
DataTable.cs
using Microsoft.AspNetCore.Components;
namespace BlazorApp1.Components
{
public partial class DataTable<TRowItem> : ComponentBase
{
[Parameter]
public IList<TRowItem> Items { get; set; } = new List<TRowItem>();
[Parameter]
public RenderFragment? ChildContent { get; set; }
private IList<Column<TRowItem>> Columns { get; set; } = new List<Column<TRowItem>>();
protected override void OnInitialized()
{
if (Items == null) Items = new List<TRowItem>();
}
protected override async Task OnParametersSetAsync()
{
await UpdateAsync().ConfigureAwait(false);
}
public async Task UpdateAsync()
{
Refresh();
}
public void Refresh()
{
InvokeAsync(StateHasChanged);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
foreach (var column in Columns)
{
column.StateChanged += ColumnStateChanged;
}
StateHasChanged();
}
}
public void Dispose()
{
foreach (var column in Columns)
{
column.StateChanged -= ColumnStateChanged;
}
Items.Clear();
}
public void AddColumn(Column<TRowItem> column)
{
Columns.Add(column);
StateHasChanged();
}
private void ColumnStateChanged(Object? sender, EventArgs args) => StateHasChanged();
}
}
数据Table.razor
@typeparam TRowItem
<h3>DataTable</h3>
<CascadingValue Value="this">
<div>
<table>
<thead>
<tr>
@foreach(var column in Columns)
{
<th nowrap>@column.CustomTitle</th>
}
</tr>
</thead>
<tbody>
@foreach(var item in Items)
{
foreach(var column in Columns)
{
<td></td>
}
}
</tbody>
<tfoot>
</tfoot>
</table>
</div>
@ChildContent
</CascadingValue>
Column.cs
using Microsoft.AspNetCore.Components;
using System.Linq.Expressions;
namespace BlazorApp1.Components
{
public partial class Column<TRowItem> : ComponentBase
{
[CascadingParameter]
private DataTable<TRowItem>? DataTable { get; set; }
[Parameter]
public string? CustomTitle { get; set; }
[Parameter]
public Expression<Func<TRowItem, object>>? Property { get; set; }
protected override Task OnInitializedAsync()
{
if (DataTable == null) throw new ArgumentNullException($"A 'DataTableColumn' must be a child of a 'DataTable' component");
DataTable.AddColumn(this);
return Task.CompletedTask;
}
public event EventHandler? StateChanged;
private void RaiseStateChanged()
{
EventHandler? handler = StateChanged;
handler?.Invoke(this, new EventArgs());
}
}
}
Column.razor
@typeparam TRowItem
Any idea why it's not the same for a custom component?
select 中的任何更改都会导致页面中的 Blazor UI 事件触发 re-render 事件。渲染器通过在组件上触发 SetParametersAsync
来完成此操作组件更新其参数,运行s OnParametersSet{Async}
和 re-renders.
DataTable
包含 Column
所以它在 re-render 前面。此时 Column
中的 CustomTitle
还没有 运行 SetParametersAsync
,所以 DataTable
呈现当前的“旧”值。 Column
然后 re-render 将 CustomTitle
设置为新值。
根据您显示的代码,答案是在 Column
组件中呈现标签。但我猜这在现实中要复杂一些,所以这可能不是答案。您可能希望它在 header 部分显示标题,在行模板中显示值。如果是这样,我可以向您指出一些说明如何执行此操作的代码。
解决方案不是做更多的管道或安装更多的东西 StateHasChanged
,而是重新考虑您的工作。获取数据,即您的 table 配置到数据 class/classes 中,并将其级联到您的各个组件。或者使用 DI 服务来保存数据并通过事件驱动组件更新。
我 运行 遇到 server-side Blazor 试图创建自定义 table 组件的问题。 table 行中的数据动态更新和更改,因此这不是问题,但如果我将 header 绑定到 属性,header 将采用之前的值属性.
从 table.razor 开始,我正在设置一个带有默认值的简单下拉列表 <select>
标记。当该值更改时,它应该更新 table header.
我添加了一个 <code>
标签和一个经典的 HTML table 作为测试,它们都正确地反映了新的 <select>
值。知道为什么自定义组件不一样吗?
Table.razor
@page "/table"
@using BlazorApp1.Data
@inject WeatherForecastService ForecastService
<select @bind="@SelectedListItem">
<option value="test">Test</option>
<option value="test2">Test2</option>
</select>
<code>@SelectedListItem</code>
<table>
<thead>
<tr>
<th>@SelectedListItem</th>
</tr>
<tbody>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
<tr>
<td>5</td>
</tr>
</tbody>
</thead>
</table>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<BlazorApp1.Components.DataTable Items=@forecasts TRowItem="WeatherForecast">
<BlazorApp1.Components.Column CustomTitle="@SelectedListItem" TRowItem="WeatherForecast"></BlazorApp1.Components.Column>
</BlazorApp1.Components.DataTable>
}
@code{
public string SelectedListItem { get; set; }
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
DataTable.cs
using Microsoft.AspNetCore.Components;
namespace BlazorApp1.Components
{
public partial class DataTable<TRowItem> : ComponentBase
{
[Parameter]
public IList<TRowItem> Items { get; set; } = new List<TRowItem>();
[Parameter]
public RenderFragment? ChildContent { get; set; }
private IList<Column<TRowItem>> Columns { get; set; } = new List<Column<TRowItem>>();
protected override void OnInitialized()
{
if (Items == null) Items = new List<TRowItem>();
}
protected override async Task OnParametersSetAsync()
{
await UpdateAsync().ConfigureAwait(false);
}
public async Task UpdateAsync()
{
Refresh();
}
public void Refresh()
{
InvokeAsync(StateHasChanged);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
foreach (var column in Columns)
{
column.StateChanged += ColumnStateChanged;
}
StateHasChanged();
}
}
public void Dispose()
{
foreach (var column in Columns)
{
column.StateChanged -= ColumnStateChanged;
}
Items.Clear();
}
public void AddColumn(Column<TRowItem> column)
{
Columns.Add(column);
StateHasChanged();
}
private void ColumnStateChanged(Object? sender, EventArgs args) => StateHasChanged();
}
}
数据Table.razor
@typeparam TRowItem
<h3>DataTable</h3>
<CascadingValue Value="this">
<div>
<table>
<thead>
<tr>
@foreach(var column in Columns)
{
<th nowrap>@column.CustomTitle</th>
}
</tr>
</thead>
<tbody>
@foreach(var item in Items)
{
foreach(var column in Columns)
{
<td></td>
}
}
</tbody>
<tfoot>
</tfoot>
</table>
</div>
@ChildContent
</CascadingValue>
Column.cs
using Microsoft.AspNetCore.Components;
using System.Linq.Expressions;
namespace BlazorApp1.Components
{
public partial class Column<TRowItem> : ComponentBase
{
[CascadingParameter]
private DataTable<TRowItem>? DataTable { get; set; }
[Parameter]
public string? CustomTitle { get; set; }
[Parameter]
public Expression<Func<TRowItem, object>>? Property { get; set; }
protected override Task OnInitializedAsync()
{
if (DataTable == null) throw new ArgumentNullException($"A 'DataTableColumn' must be a child of a 'DataTable' component");
DataTable.AddColumn(this);
return Task.CompletedTask;
}
public event EventHandler? StateChanged;
private void RaiseStateChanged()
{
EventHandler? handler = StateChanged;
handler?.Invoke(this, new EventArgs());
}
}
}
Column.razor
@typeparam TRowItem
Any idea why it's not the same for a custom component?
select 中的任何更改都会导致页面中的 Blazor UI 事件触发 re-render 事件。渲染器通过在组件上触发 SetParametersAsync
来完成此操作组件更新其参数,运行s OnParametersSet{Async}
和 re-renders.
DataTable
包含 Column
所以它在 re-render 前面。此时 Column
中的 CustomTitle
还没有 运行 SetParametersAsync
,所以 DataTable
呈现当前的“旧”值。 Column
然后 re-render 将 CustomTitle
设置为新值。
根据您显示的代码,答案是在 Column
组件中呈现标签。但我猜这在现实中要复杂一些,所以这可能不是答案。您可能希望它在 header 部分显示标题,在行模板中显示值。如果是这样,我可以向您指出一些说明如何执行此操作的代码。
解决方案不是做更多的管道或安装更多的东西 StateHasChanged
,而是重新考虑您的工作。获取数据,即您的 table 配置到数据 class/classes 中,并将其级联到您的各个组件。或者使用 DI 服务来保存数据并通过事件驱动组件更新。