Javascript Blazor WebAssembly中的JSRuntime在第一次加载时阻止DOM更新

Javascript Blazor WebAssembly中的JSRuntime在第一次加载时阻止DOM更新,javascript,asp.net-core,blazor,blazor-client-side,blazor-jsinterop,Javascript,Asp.net Core,Blazor,Blazor Client Side,Blazor Jsinterop,我用的是最新的 根据一个普通的Blazor WASM模板,我做了以下调整来测试/调试该问题: 在Program.cs中,我添加了以下将注入所有页面的单例: builder.Services.AddSingleton<ApplicationState>(); @inject IJSRuntime JSRuntime @inject BlazorApp1.State.ApplicationState AppState; 并与JSRuntime一起注入到Imports.razor中,因

我用的是最新的

根据一个普通的Blazor WASM模板,我做了以下调整来测试/调试该问题:

Program.cs中,我添加了以下将注入所有页面的单例:

builder.Services.AddSingleton<ApplicationState>();
@inject IJSRuntime JSRuntime
@inject BlazorApp1.State.ApplicationState AppState;
并与JSRuntime一起注入到Imports.razor中,因此它们可用于所有页面:

builder.Services.AddSingleton<ApplicationState>();
@inject IJSRuntime JSRuntime
@inject BlazorApp1.State.ApplicationState AppState;
为了测试ApplicationState对象是否正确通过应用程序,我在以下两个位置使用了它:

NavManu.razor中,我将其中一个菜单项环绕BoolValue以显示/隐藏该项:

@if (AppState.BoolValue)
{
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="counter">
            <span class="oi oi-plus" aria-hidden="true"></span> Counter
        </NavLink>
    </li>
}
一切正常,菜单项以及H2标记中的字符串值可见

但是,如果我通过JSRuntime向Javasript函数添加调用,如下所示:

@code
{
    protected override async Task OnInitializedAsync()
    {
        var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");

        AppState.BoolValue = true;
        AppState.StringValue = "Testing 123...";
    }
}
@code
{
受保护的重写异步任务OnInitializedAsync()
{
var jwtToken=await JSRuntime.InvokeAsync(“getCookie”、“cookieName”);
AppState.BoolValue=true;
AppState.StringValue=“测试123…”;
}
}
javascript运行,AppState值更新(我添加了一个控制台日志来测试这一点)但是导航不更新以显示菜单项,H2标记不显示新的字符串值

如果您导航到菜单中的某个页面,状态将更新,但如果您进行硬刷新,状态将不会再次更新

我尝试添加一个statehaschange()调用,但没有成功:

@code
{
    protected override async Task OnInitializedAsync()
    {
        var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");

        AppState.BoolValue = true;
        AppState.StringValue = "Testing 123...";
        StateHasChanged(); // Does not work!
    }
}
@code
{
受保护的重写异步任务OnInitializedAsync()
{
var jwtToken=await JSRuntime.InvokeAsync(“getCookie”、“cookieName”);
AppState.BoolValue=true;
AppState.StringValue=“测试123…”;
StateHasChanged();//不工作!
}
}
由于导航后状态似乎会更新,我也尝试强制执行导航事件,但也没有效果:

@code
{
    protected override async Task OnInitializedAsync()
    {
        var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");

        AppState.BoolValue = true;
        AppState.StringValue = "Testing 123...";

        var uri = new Uri(NavigationManager.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
        NavigationManager.NavigateTo(uri);
    }
}
@code
{
受保护的重写异步任务OnInitializedAsync()
{
var jwtToken=await JSRuntime.InvokeAsync(“getCookie”、“cookieName”);
AppState.BoolValue=true;
AppState.StringValue=“测试123…”;
var uri=新uri(NavigationManager.uri).GetComponents(UriComponents.PathAndQuery,UriFormat.Unescaped);
NavigationManager.NavigateTo(uri);
}
}
我正在继续尝试其他一些东西,但似乎没有任何效果StateHasChanged()在我的理解中,确实应该是解决类似问题的方法。还有什么我应该试试的吗

更新

正如enet指出的,当状态发生变化时,我需要通知下游组件。引起问题的不是JSRuntime,而是它在App.razor中创建的延迟,导致在硬刷新时对象状态看起来不同。在第一个场景中,对象在其他组件开始渲染之前更新,因此它们使用已更新的状态。但是一旦出现任何类型的延迟(例如调用JSRuntime),他们就需要一个通知来更新状态,因为他们有机会在延迟之前使用较旧的状态进行渲染。我用几毫秒的Task.Delay()替换了JSRuntime调用,结果是同样的问题!向事件添加触发器解决了此问题。

MessageService.cs 注意:该服务是一个普通类。。。它向其他对象提供服务,应该将其添加到Startup.ConfigureServices方法中的DI容器中,以使其可供请求客户端使用。将以下内容添加到ConfigureServices方法:

 services.AddScoped<MessageService>();
services.addScope();
注意:如您所见,我定义了一个Action类型的事件委托,当用户在Component3的文本框中键入文本时,将从属性的set访问器调用该委托。触发此委托会导致Component3输入的文本显示在作为Component2父级的索引组件中(请参见下面的代码)

剃刀索引
@page/“
@注入消息服务消息服务
@实现IDisposable
@注入IJSRuntime JSRuntime

我是Component2的家长。我有我的祖父的口信
孩子:@MessageService.Message

@jwtToken

@代码{ 私有字符串jwtToken; 受保护的重写异步任务OnInitializedAsync() { //等待SetTokenAsync(“我的令牌”); jwtToken=await GetTokenAsync(); MessageService.Notify+=OnNotify; MessageService.Message=“来自索引的消息”; } 公共无效通知() { InvokeAsync(()=> { StateHasChanged(); }); } 公共空间处置() { MessageService.Notify-=OnNotify; } 公共异步任务GetTokenAsync() =>等待JSRuntime.InvokeAsync(“localStorage.getItem”、“authToken”); 公共异步任务SetTokenAsync(字符串令牌) { if(标记==null) { 等待JSRuntime.InvokeAsync(“localStorage.removietem”、“authToken”); } 其他的 { 等待JSRuntime.InvokeAsync(“localStorage.setItem”,“authToken”,token); } } }
注意,我们直接绑定到MessageService.Message属性,但是必须调用StateHasChanged方法来刷新文本的显示

组件2.3
Component2:我是component3的父母
@代码{
}
组件3.3
@injectmessageservice
这是组件3。请键入一条消息给我的祖父母

@代码{ 受保护的覆盖无效OnInitialized() { MessageService.Notify+=()=>InvokeAsync(()=> { StateHasChanged(); }); } }
请注意,在Component3中,我们将MessageService.Message绑定到一个文本框,每次按键盘时都会发生绑定(输入事件与更改) 事件)

仅此而已,希望这对您有所帮助,请随时提问。

MessageService.cs 注:该服务是一项规范
@code
{
    protected override async Task OnInitializedAsync()
    {
        var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");

        AppState.BoolValue = true;
        AppState.StringValue = "Testing 123...";
        StateHasChanged(); // Does not work!
    }
}
@code
{
    protected override async Task OnInitializedAsync()
    {
        var jwtToken = await JSRuntime.InvokeAsync<string>("getCookie", "cookieName");

        AppState.BoolValue = true;
        AppState.StringValue = "Testing 123...";

        var uri = new Uri(NavigationManager.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
        NavigationManager.NavigateTo(uri);
    }
}
 public class MessageService
{
    private string message;
    public string Message
    {
        get => message;
        set
        {
            if (message != value)
            {
                message = value;
                if (Notify != null)
                {
                     Notify?.Invoke();
                }

            }
        }
    }

    public event Action Notify;
}
 services.AddScoped<MessageService>();
  @page "/"

  @inject MessageService MessageService
  @implements IDisposable
  @inject IJSRuntime  JSRuntime

  <p>
   I'm the parent of Component2. I've got a message from my grand 
   child: @MessageService.Message
  </p>

  <Component2 />
  <p>@jwtToken</p>

  @code {
    private string jwtToken;

    protected override async Task OnInitializedAsync()
   {
       // await SetTokenAsync("my token");
       jwtToken = await GetTokenAsync();
       MessageService.Notify += OnNotify;
       MessageService.Message = "A message from Index";
    }

    public void OnNotify()
    {
    InvokeAsync(() =>
    {
        StateHasChanged();
    });
}

public void Dispose()
{
    MessageService.Notify -= OnNotify;
}

public async Task<string> GetTokenAsync()
       => await JSRuntime.InvokeAsync<string>("localStorage.getItem", "authToken");

public async Task SetTokenAsync(string token)
{
    if (token == null)
    {
        await JSRuntime.InvokeAsync<object>("localStorage.removeItem", "authToken");
    }
    else
    {
        await JSRuntime.InvokeAsync<object>("localStorage.setItem", "authToken", token);
    }


 }
}
<h3>Component2: I'm the parent of component three</h3>
<Component3/>

@code {

}
 @inject MessageService MessageService

 <p>This is component3. Please type a message to my grand parent</p>
 <input placeholder="Type a message to grandpa..." type="text"
   @bind="@MessageService.Message" @bind:event="oninput" />

 @code {

     protected override void OnInitialized()
     {

        MessageService.Notify += () => InvokeAsync(() =>
        {
           StateHasChanged();
        });

     }
 }