Blazor表单提交需要两次点击才能刷新视图。

6

我有一个Blazor组件,正在测试API调用以获取天气数据。不知何故,必须单击两次提交按钮,才能显示更新后的对象属性。

页面初始化时,我可以很好地从浏览器中获取位置,并且天气数据可以正常显示在表格中。

FetchData.razor

@page "/fetchdata"
@using BlazingDemo.Client.Models
@using AspNetMonsters.Blazor.Geolocation

@inject HttpClient Http
@inject LocationService LocationService

<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>

<div>
    <EditForm Model="@weatherForm" OnValidSubmit="@GetWeatherDataAsync">
        <DataAnnotationsValidator />
        <ValidationSummary />
        <InputText Id="cityName" bind-Value="@weatherForm.CityName" />
        <button type="submit">Submit</button>
    </EditForm>
</div>
<br />

@if (weatherData == null)
{
    <Loader/>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>City</th>
                <th>Conditions</th>
                <th>Temp. (C)</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>@weatherData.Name</td>
                <td>@weatherData.Weather[0].Main</td>
                <td>@weatherData.Main.Temp</td>
            </tr>
        </tbody>
    </table>
}

@functions {

    WeatherFormModel weatherForm = new WeatherFormModel();
    WeatherData weatherData;
    Location location;

    protected override async Task OnInitAsync()
    {
        location = await LocationService.GetLocationAsync();

        if (location != null)
        {
            weatherData = await GetWeatherDataAsync(location.Latitude,location.Longitude);
        }
    }

    protected async Task<WeatherData> GetWeatherDataAsync(decimal latitude, decimal longitude)
    {
        return await Http.GetJsonAsync<WeatherData>($"https://api.openweathermap.org/data/2.5/weather?lat={location.Latitude}&lon={location.Longitude}&units=metric&appid=***removed***");
    }

    protected async void GetWeatherDataAsync()
    {
        weatherData = await Http.GetJsonAsync<WeatherData>($"https://api.openweathermap.org/data/2.5/weather?q={weatherForm.CityName}&units=metric&appid=***removed***");
    }
}

WeatherFormModel.cs

namespace BlazingDemo.Client.Models
{
    public class WeatherFormModel
    {
        [Required, Display(Name = "City Name")]
        public string CityName { get; set; }

        public bool IsCelcius { get; set; }
    }
}

我正在调用 GetWeatherDataAsync() 方法,并且通过检查 Chrome 返回的 JSON 数据,可以看到每次调用都会返回数据。但是在第一次单击时它不会更新表格。我已经尝试在调用该方法之前将 weatherData 设置为 null,但那也不起作用。
有什么建议吗?
2个回答

9
在这里必须调用StateHasChanged()的原因是,你所提到的GetWeatherDataAsync()方法是void类型的,因此服务器无法知道何时调用已经完成。 因此,原始表单提交请求比填充天气数据更早完成。 因此,调用StateHasChanged()会告诉服务器有必要重新呈现组件,并且这将正常工作。
一般来说,应该避免使用async void(除非确实是fire-and-forget情况),而应该返回某种类型的Task,这样就可以消除对显式StateHasChanged()调用的需求。
下面,我只是简单地更改了你的GetWeatherDataAsync方法,使其返回Task而不是void:
protected async Task GetWeatherDataAsync()
{
    weatherData = await Http.GetJsonAsync<WeatherData>($"https://api.openweathermap.org/data/2.5/weather?q={weatherForm.CityName}&units=metric&appid=***removed***");
}

1
这个 GitHub 问题 似乎表明 Blazor 支持异步事件处理程序,并且在 MSDN 文档 中也有提到。这意味着这是正确的答案。 - ColinM
@Artak - 在这个例子中,重构后的代码会是什么样子? - Kixoka
现在将其添加到我的答案中。 - Artak

7
当你点击“提交”按钮时,GetWeatherDataAsync()方法被调用,并将数据检索到weatherData变量中...然后它就完成了它的任务。
通常情况下,在触发事件后组件会重新渲染;也就是说,不需要手动调用StateHasChanged()方法来重新渲染组件。Blazor会自动调用它。但是在这种情况下,你需要手动添加StateHasChanged()方法以重新渲染组件。因此你的代码应该像这样:
protected async void GetWeatherDataAsync()
    {
        weatherData = await Http.GetJsonAsync<WeatherData>($"https://api.openweathermap.org/data/2.5/weather?q={weatherForm.CityName}&units=metric&appid=***removed***");

        StateHasChanged();
    }

“提交”事件是 Blazor 中唯一默认被 Blazor 阻止其动作的事件。实际上,“提交”事件并没有将表单真正地提交给服务器。详见:https://github.com/aspnet/AspNetCore/blob/master/src/Components/Browser.JS/src/Rendering/BrowserRenderer.ts。所以,我猜想 Blazor 对此事件进行了特殊处理,并且不会调用 StateHasChanged 方法来刷新组件。

哇,那应该成为我们公共文档的一部分。我会提出建议的更改。干杯。 - Jason Shave
非常好的帖子,我以为我的表单没有提交或者需要点击两次,但是仔细检查断点后发现每次都有提交。结果证明只是需要这个来显示我的确认信息。 - Joel Youngberg
很棒的帖子,我以为我的表单没有提交或者需要点击两次,但是通过断点调试仔细检查后发现每次都成功提交了。原来只是需要这样才能显示我的确认信息。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接