为啥 Blazor 生命周期方法会执行两次?

Posted

技术标签:

【中文标题】为啥 Blazor 生命周期方法会执行两次?【英文标题】:Why are Blazor lifecycle methods getting executed twice?为什么 Blazor 生命周期方法会执行两次? 【发布时间】:2020-01-24 07:15:11 【问题描述】:

因此,随着 asp.net core 3.0 和 blazor 1.0 的发布,我开始使用 blazor 进行一些实际工作。将 Blazor 组件代码拆分为后面的代码时,我使用以下代码

 public class LogoutModel : BlazorComponent
    

不幸的是,BlazorComponent 不再存在,所以我转向 ComponentBase。不知道这种变化是什么时候发生的..

现在我的其余代码如下所示

 public class LogoutModel : ComponentBase
    

        protected override async Task OnInitializedAsync()
        
        

        protected override async Task OnParametersSetAsync()
        
        
    

我注意到生命周期方法按以下顺序执行 OnInitializedAsync() OnParametersSetAsync() OnInitializedAsync() OnParametersSetAsync()

我不太确定为什么每个方法都会执行两次。

这就是我的 Blazor 文件的样子

@page  "/account/logout"
@inherits LogoutModel

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    Logout page
</body>
</html>

【问题讨论】:

您使用的是哪种托管模式? 我正在使用 blazor 服务器 【参考方案1】:

TL;DR

这是因为预渲染机制将组件初始化为主机页面_Host.cshtml 的一部分,因此第一个 http 请求将导致主机页面不仅作为 blazor 应用程序的脚本加载器,而且还有静态渲染的视图。因此,用户无需等待大致以下步骤即可看到初始视图:

一个 WebSocket 连接由 SignalR 建立。

从服务器接收第一束渲染指令。

渲染指令应用于视图。

这不仅可以缩短用户看到初始视图之前的响应延迟,还可以使SEO受益。在 blazor 应用程序正常启动后,预呈现的视图将被真正的 blazor 组件替换。

新项目模板中默认启用预渲染功能,因此您必须选择以下一项:

正确处理该组件被预渲染的情况(可能通过检查 IJSRuntime 是否能够从依赖注入中解决)。

通过修改_Host.cshtml禁用预渲染功能,替换

<component type="typeof(App)" render-mode="ServerPrerendered" />

<component type="typeof(App)" render-mode="Server" />

对于旧版本,替换

@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))

@(await Html.RenderComponentAsync<App>(RenderMode.Server))

原始答案

我对一个全新的blazorserver 项目进行了测试,记录了何时调用生命周期方法,并得到了以下输出:

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using 'C:\Users\Alsein\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\repos\HelloWorld
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/_Host'
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
      Route matched with page = "/_Host". Executing page /_Host
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[103]
      Executing an implicit handler method - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[104]
      Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
crit: HelloWorld.MyBase[0]
      OnInitializedAsync
crit: HelloWorld.MyBase[0]
      OnParameterSetAsync
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
      Executed page /_Host in 122.3724ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint '/_Host'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 216.7341ms 200 text/html; charset=utf-8
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/site.css
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/_framework/blazor.server.js
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/bootstrap/bootstrap.min.css
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/site.css'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\site.css'
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/_framework/blazor.server.js'. Physical path: 'N/A'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 44.733000000000004ms 200 text/css
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/bootstrap/bootstrap.min.css'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\bootstrap\bootstrap.min.css'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 55.3613ms 200 text/css
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 55.569900000000004ms 200 application/javascript
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/open-iconic/font/css/open-iconic-bootstrap.min.css
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/open-iconic/font/css/open-iconic-bootstrap.min.css'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 4.5189ms 200 text/css
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 POST https://localhost:5001/_blazor/negotiate text/plain;charset=UTF-8 0
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/css/open-iconic/font/fonts/open-iconic.woff
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
      Sending file. Request path: '/css/open-iconic/font/fonts/open-iconic.woff'. Physical path: 'D:\repos\HelloWorld\wwwroot\css\open-iconic\font\fonts\open-iconic.woff'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 4.3562ms 200 application/font-woff
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/_blazor/negotiate'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint '/_blazor/negotiate'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 24.7409ms 200 application/json
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/1.1 GET https://localhost:5001/_blazor?id=7oyJvbydrUy9tqlsH_DHzQ
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/_blazor'
crit: HelloWorld.MyBase[0]
      OnInitializedAsync
crit: HelloWorld.MyBase[0]
      OnParameterSetAsync

从结果可以看出,组件被加载了两次。

第一次被/_Host请求和处理页面时直接作为简单的Mvc组件加载,必须在_Host.cshtml中使用以下代码指定,第一次调用生命周期方法:
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))

然后加载资源,包括blazor.server.js

然后 blazor 应用开始渲染。

然后将组件作为 blazor 组件加载,第二次调用生命周期方法。

尝试将RenderMode.ServerPrerendered 替换为RenderMode.Server,然后它的行为符合预期,即生命周期方法仅被调用一次(当 blazor 应用启动时)。

结论: 默认的RenderModeServerPrerendered,这一定意味着 Mvc 可以将组件渲染为静态内容,以便在 blazor 应用程序下载并启动之前显示页面内容,然后当 blazor 应用程序启动时,它会接管页面内容.这必须是一种用户体验的解决方法,浏览器用户可以等待更少的时间来查看内容。

【讨论】:

感谢您花时间研究这个问题,这对我帮助很大。 谢谢,这是MS Documentation 如果这是 Nuxt.js,这将类似于混合模式,其中组件或页面的某些部分可以在服务器上可控地预渲染,以加快速度,或使用某些服务那仅在服务器端可用(或应该仅在服务器端可用)。必须有一种方法可以检查组件的渲染位置,在生命周期方法中,我想我在某个地方看到过,但我现在不记得了。然后,您可以简单地进行 if 检查并相应地继续。 如果我可以添加一些东西,这就是你必须在 元素上的 _Host.cshtml 文件中准确更改的内容:render-mode="ServerPrerendered"render-mode="Server" --- see here跨度> @KhalidAb 答案是在 blazor 的第一个 GA 版本发布之前编写的。答案已更新。【参考方案2】:

我在一个网站的保留页面上遇到了完全相同的问题,我有一个小 css 动画,它在开发中看起来很棒,但是当我把它放上去时它运行了两次。将 RenderMode 更改为 Server 确实可以解决问题,但它看起来明显更慢。

很有趣,因为在我这样做之前我永远不会选择这个,对于最终站点,我将切换回 ServerPrerendered

【讨论】:

我切换到“服务器”但仍然调用了两次。protected override async Task OnInitializedAsync() await GetData() ??? @Jon - 请参阅下面的答案 - 它解决了您的问题【参考方案3】:

您可以使用 IJSRuntime 调用一些方法或运行一些 JS 代码一次,这样做您可以检查属性 IsFirtRender 只执行一次某些方法,但它不适用于所有方法,有时如果使用该方法则不会渲染前

【讨论】:

【参考方案4】:

有同样的问题。 对我来说,&lt;script src="_framework/blazor.server.js"&gt;&lt;/script&gt;_Host.cshtml 上被写了两次

【讨论】:

【参考方案5】:

在这里被跟踪:https://github.com/dotnet/aspnetcore/issues/21348

同时:

(Blazor WASM) - 更改自

protected override async Task OnParametersSetAsync()
    await ReloadServerData();

到参数设置器:

[Parameter]
public string Foo

    get => _foo;
    set
    
        // if you put a breakpoint here, you will realize, that this setter
        // gets called multiple times, but only once actually changes value
        if (_foo == value)
            return;

        _foo = value;

        ReloadServerData();
    

完全意识到,当 ReloadServerData() 失败时,该属性也无法设置值。

【讨论】:

如果我的参数是复杂类型怎么办?前任。类的类型 这取决于你,我会说这取决于具体情况来决定对象是否改变 - 检查例如***.com/questions/10454519/… 或者 - 如果您需要检测某些复杂对象的特定属性的变化,请使用自定义事件然后订阅它。

以上是关于为啥 Blazor 生命周期方法会执行两次?的主要内容,如果未能解决你的问题,请参考以下文章

Blazor、对象生命周期和依赖注入

Activity 横竖屏生命周期

Blazor University (42)JavaScript 互操作 —— 生命周期和内存泄漏

横竖屏切换时候activity的生命周期

ASP.NET Core Blazor 服务器中的实体框架上下文生命周期

关闭飞行模式后,为啥 Android 应用程序会通过 Activity 和 Fragment 生命周期方法