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 服务来保存数据并通过事件驱动组件更新。