参数更改时如何重新渲染 Blazor 组件

ele*_*tra 8 c# blazor blazor-server-side

我有两个 Blazor 组件。第一个组件仅显示来自 JSON api 的学生列表

<select @onchange="selectStudent"> 
  @foreach(var student in students) {
    <option value="@student.id"> @student.name /option>
  }
</select>

@code {
    var API = "https://abcd.com/students/"
    students = // variable stores JSON data of students used in the foreach loop


    // sending select event to parent
    [Parameter]
    public EventCallBack<string> OnStudentSelect { get; set; }
    public async Task SelectStudent(ChangeEventArgs e) {
        await OnStudentSelect.InvokeAsync(e.Value.ToString())
    }
}
Run Code Online (Sandbox Code Playgroud)

当用户从下拉列表中选择学生时,我想捕获 Student.id 并将其作为参数发送到另一个组件

@page "/student"

<Students OnStudentSelect="@GetStudentId"> </Students>     

<p> Displaying profile of StudentID: @studentId </p>
<Student StudentId="@StudentId"> </Student>

@code{
    private int StudentId = 1; 

    private void GetStudentId(int _id) {
        studentId = _id
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的代码片段,但它有效,我可以看到<p></p>标签内的消息发生变化。

我遇到的问题是这样的:

<Student StudentId="StudentId"> </Student>
Run Code Online (Sandbox Code Playgroud)

由于某种原因,组件在收到新消息时不会更新StudentId

有关的文档StateHasChanged()尚不清楚,但将其放入内部Student似乎也无法解决问题。

小智 14

阅读您的帖子和评论后,我决定编写一些组件,我认为这些组件将 1. 回答您的问题,2. 向您展示将来编写 blazor 组件的好方法。让我们从第一个组件 StudentSelector.razor 开始:

StudentSelector.razor

@using BlazorAnswers.Models

@*Show loading content while students are loading*@
@if(!StudentList.Any())
{
    @LoadingContent
}
else
{
    <label>Select a Student: </label>
    <select @onchange="HandleStudentChanged" class="form-select w-25">
        @foreach (var student in StudentList)
        {
            <option value="@student.Id">@student.Name</option>
        }
    </select>
}

@code {
    [Parameter] public EventCallback<StudentModel> OnStudentChanged { get; set; }

    [Parameter] public IEnumerable<StudentModel> StudentList { get; set; } = Enumerable.Empty<StudentModel>();

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

    /// <summary>
    /// This transforms the ChangeEventArgs into a StudentModel
    /// </summary>
    private async Task HandleStudentChanged(ChangeEventArgs args)
    {
        if (OnStudentChanged.HasDelegate)
        {
            if (args is not null && int.TryParse((string)args.Value, out var id))
            {
                var selectedStudent = StudentList.FirstOrDefault(a => a.Id.Equals(id));

                if (selectedStudent is not null)
                    await OnStudentChanged.InvokeAsync(selectedStudent);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您应该拥有的下一个组件是显示所选学生的组件:

StudentDisplay.razor

@using BlazorAnswers.Models

@if (SelectedStudent is not null)
{
    @StudentContent(SelectedStudent)
}

@code {
    [Parameter] public StudentModel? SelectedStudent { get; set; }

    [Parameter] public RenderFragment<StudentModel> StudentContent { get; set; } = default!;
}

Run Code Online (Sandbox Code Playgroud)

最后,所有内容都放在 Student.razor 页面中,这是一个可导航页面:

学生剃刀

@page "/student"
@using BlazorAnswers.Models

<StudentSelector OnStudentChanged="HandleStudentSelected"
                 StudentList="@_students">
    <LoadingContent>
        Loading Students...
    </LoadingContent>
</StudentSelector>

@if (_selectedStudent is not null)
{
    <div class="container-lg pt-4">
        <StudentDisplay SelectedStudent="@_selectedStudent">
            <StudentContent Context="student">
                <p> Displaying profile of Id: @student.Id </p>
                <p> Displaying profile of Name: @student.Name </p>
            </StudentContent>
        </StudentDisplay>
    </div>
}

@code {
    private StudentModel _selectedStudent;
    private IEnumerable<StudentModel> _students = Enumerable.Empty<StudentModel>();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            await GetStudents();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    
    /// <summary>
    /// Method that will be invoked in the StudentSelector.razor component
    /// </summary>
    private void HandleStudentSelected(StudentModel selectedStudent)
    {
        _selectedStudent = selectedStudent;
    }
    
    /// <summary>
    /// Method for retrieving students and is intentionally delayed
    /// </summary>    
    private async Task GetStudents()
    {
        await Task.Delay(3000);

        _students = _selectableStudents;
    }

    /// <summary>
    /// Test Data to use as an example
    /// </summary>
    private static IEnumerable<StudentModel> _selectableStudents = new List<StudentModel>()
    {
        new StudentModel{ Id = 1, Name = "Joe" },
        new StudentModel{ Id = 2, Name = "Amy" },
        new StudentModel{ Id = 3, Name = "Beth" },
        new StudentModel{ Id = 4, Name = "Kevin" },
        new StudentModel{ Id = 5, Name = "Carl" },
        new StudentModel{ Id = 6, Name = "Chad" },
        new StudentModel{ Id = 7, Name = "Bryan" },
        new StudentModel{ Id = 8, Name = "Kelly" },
        new StudentModel{ Id = 9, Name = "Steve" },
        new StudentModel{ Id = 10, Name = "Doug" },
    };
}

Run Code Online (Sandbox Code Playgroud)

然后是 StudentModel 类:

StudentModel.cs

public class StudentModel
{
  public int Id { get; set; }
  public string Name { get; set; } = string.Empty;
}
Run Code Online (Sandbox Code Playgroud)

设计 blazor 应用程序时要考虑的另一件事是让组件保持愚蠢而页面保持智能。这意味着当注入服务时,尝试将它们保留在页面中,而不是让它们进入组件本身。这避免了很多复杂的情况,并允许您构建可重用的组件。我知道有人说过关于调用 StateHasChanged 的​​事情,但仅当 UI 因非人类交互而发生更改时才尝试使用它。在此输入图像描述