【问题标题】: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 服务器

标签: blazor blazor-server-side


【解决方案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() 失败时,该属性也无法设置值。

      【讨论】:

      • 如果我的参数是复杂类型怎么办?前任。类的类型
      • 这取决于你,我会说这取决于具体情况来决定对象是否改变 - 检查例如stackoverflow.com/questions/10454519/…
      • 或者 - 如果您需要检测某些复杂对象的特定属性的变化,请使用自定义事件然后订阅它。
      猜你喜欢
      • 2020-11-19
      • 2020-11-10
      • 2020-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-19
      • 2021-05-15
      • 1970-01-01
      相关资源
      最近更新 更多