Blazor ServerPrerendered模式OnInitialized{Async}执行两次
创建Blazor应用,刷新页面调试时发现OnInitialized会执行两次。 这里需要注意,进入这个站点的第一个页面的OnInitialized会被执行两次,例如我在浏览器输入URL进去了A页面,那么A页面的OnInitialized会执行两次。然后我通过A页面上的Link进入B页面,B页面的OnInitialized只会执行一次。
OnInitialized执行两次的原因
查看微软Blazor文档作了如下解释:
预呈现后的有状态重新连接
在 Blazor Server 应用中,当 RenderMode 为 ServerPrerendered 时,组件最初作为页面的一部分静态呈现。 浏览器重新建立与服务器的 SignalR 连接后,将再次呈现组件,并且该组件为交互式。 如果存在用于初始化组件的 OnInitialized{Async} 生命周期方法,则该方法执行两次:
在静态预呈现组件时执行一次。
在建立服务器连接后执行一次。
在最终呈现组件时,这可能导致 UI 中显示的数据发生明显变化。 若要避免在 Blazor Server 应用中出现此双重呈现行为,请传递一个标识符以在预呈现期间缓存状态并在预呈现后检索状态。
一开始看这个文档有点难以理解,我们用通俗的话来解释一下:
预呈现ServerPrerendered是一个网页的所有元素都在服务器上编译并将静态 HTML 提供给客户端的过程。
ServerPrerendered模式下第一次调用OnInitialized发生再服务器上,服务器必须完成创建静态 html 网站的所有工作,并将内容发送给用户后,第二个 OnInitialized开始执行。
ServerPrerendered用于帮助 SPA(单页应用程序)改进其 SEO(搜索引擎优化)。 另一个好处是网站加载速度似乎更快。这一切都发生在非常短的时间内,大多数最终用户是无法察觉的。
直观的来看,如果使用ServerPrerendered模式,你右键网页查看源文件那么它是输出服务端渲染的内容的。如果使用Server模式,那么右键查看源文件没有实际内容。
如何避免OnInitialized执行两次
将render-mode改为server(SEO不友好)
根据文档的提示,我们可以通过修改_Host.cshtml里的render-mode="Server"来避免OnInitialized执行两次。
<component type="typeof(App)" render-mode="ServerPrerendered" />
// 修改为
<component type="typeof(App)" render-mode="Server" />
这样修改后刷新页面只会执行一次OnInitialized。但是,如果我们对SEO搜索引擎优化有要求,那么这样做显然是不行的。
持久保存预呈现组件的状态
之前我一直无法理解“传递一个标识符以在预呈现期间缓存状态并在预呈现后检索状态”,后来看到了persist-component-state提供的解决方案。它的用法也比较简单,先在_Host.razor中添加标签<persist-component-state />:
<body>
<component type="typeof(App)" render-mode="ServerPrerendered" />
...
<persist-component-state />
</body>
然后page页面的示例代码是这样的:
@implements IDisposable
@inject PersistentComponentState ApplicationState
<h1>Hello, world!</h1>
<p>Status: @Status</p>
@code {
public string? Status { get; set; } = "loading";
private PersistingComponentStateSubscription persistingSubscription;
protected override async Task OnInitializedAsync()
{
persistingSubscription =
ApplicationState.RegisterOnPersisting(PersistData);
if (!ApplicationState.TryTakeFromJson<string>(
"Status", out var restored))
{
Status = await GetStatus();
}
else
{
Status = restored!;
}
}
private async Task<string> GetStatus()
{
await Task.Delay(3000);
return "loaded";
}
private Task PersistData()
{
ApplicationState.PersistAsJson("Status", Status);
return Task.CompletedTask;
}
void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}
示例代码里GetStatus方法模拟需要花费时间的查询,所以故意延迟3秒返回结果。
这样刷新页面时,实际执行了两次OnInitializedAsync,第一次在服务端执行OnInitializedAsync时调用GetStatus查询并缓存了Status,第二次执行OnInitializedAsync时直接从缓存拿到了Status,这样可以避免第二次重复查询浪费性能。这里不需要担心时效性问题,这个缓存范围仅在这个请求,请注意实现了IDisposable接口,会Dispose掉。再次刷新页面时还会调用GetStatus查询并缓存Status,第二次再直接从缓存里拿Status。如果从别的页面跳转过来,只会执行一次OnInitializedAsync并且执行到GetStatus。
用户体验上来看,第一次打开这个页面,白屏loading了3秒,然后输出页面,页面上显示的是Status: loaded。 也许会有人觉得奇怪,那还要loading状态做什么?
是这样的,如果用户先打开了另外一个页面,然后那个页面上有一个Link到上面示例代码的页面,那么点Link过来的时候,页面不需要白屏加载,而是直接先显示Status: loading, 然后等3秒后变成Status: loaded。 而且OnInitializedAsync只执行了一次。
如果改成render-mode="Server"后,用户体验是这样的:
页面不会白屏等待(不会Delay等待3秒),而是直接输出Status: loading,然后执行OnInitializedAsync等待3秒得到Status后页面变成Status:loaded。右键查看网页源文件的话没有实际输出内容(SEO不友好)。
下面两个图是ServerPrerendered和Server两种模式下查看网页源文件的区别,大家可以对比一下:
以上是作者做了很多调试后得到的一些结果,希望对大家理解Blazor的ServerPrerendered和Server的区别有所帮助。如果有什么错误或者更好的建议,请留言分享。
更新于:1个月前相关文章
- SQL Server EF使用Sequence全局自增ID
- SQL Server用UUID做主键性能问题和解决方案
- 用Blazor开发App应用可行吗?
- 前端开发有必要学习Blazor吗?
- Blazor的N种渲染模式原理和常见问题说明
- 数据库SQL Server2014和SQL Server2019的区别和如何选择?
- .NET Blazor 2024年发展趋势
- Blazor获取Url路由参数的方法
- ASP.NET Core Blazor EditForm内置表单验证显示ValidationMessage
- Blazor NavigateTo报错Microsoft.AspNetCore.Components.NavigationException:“Exception_WasThrown”
- Blazor的5种render-mode的区别
- Blazor使用内存中状态容器服务保存和验证登陆状态
- MySQL server has gone away
- .NET的Razor和Blazor有什么区别和联系?
- .NET的Blazor值得学习吗?Blazor的优缺点和使用场景
- Blazor适合大型项目吗?
- .NET8 Blazor三种模式的区别和使用场景
- 在 Windows Server Server/Windows 11 上安装 VirtIO 驱动程序
- .NET用Blazor的公司多吗?
- .NET8 Blazor的Auto渲染模式