JSRuntime в Blazor WebAssembly блокирует обновления DOM при первой загрузке - PullRequest
0 голосов
/ 08 мая 2020

Я использую последнюю версию Blazor WebAssembly 3.2.0 Release Candidate

Из ванильного шаблона Blazor WASM я внес следующие изменения для тестирования / отладки проблемы:

В Program.cs Я добавляю следующий синглтон, который будет вставлен на все страницы:

builder.Services.AddSingleton<ApplicationState>();

Класс выглядит так:

public class ApplicationState
{
    public bool BoolValue { get; set; }
    public string StringValue { get; set; }

    public ApplicationState()
    {
        BoolValue = false;
        StringValue = "Default value";
    }

}

и введен в _Imports.razor вместе с JSRuntime, поэтому они доступны для всех страниц:

@inject IJSRuntime JSRuntime
@inject BlazorApp1.State.ApplicationState AppState;

Чтобы проверить правильность прохождения объекта ApplicationState через приложение, я использовал его в следующие 2 места:

В 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>
}

И в Index.razor я добавил тег H2 под заголовком Hello World по умолчанию, чтобы отобразить StringValue

<h1>Hello, world!</h1>
<h2>@AppState.StringValue</h2>

У меня также есть javascript файл, расположенный в wwwroot/js/extenstions.js, он имеет единственную функцию, используемую для приготовления пищи * 1 086 * data:

function getCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
};

И включен как скрипт в wwwroot/index.html прямо под _framework / blazor.webassembly. js скриптом:

<script src="_framework/blazor.webassembly.js"></script>
<script src="js/extensions.js"></script>

В App.razor У меня есть следующий @ code блок:

@code
{
    protected override async Task OnInitializedAsync()
    {
        AppState.BoolValue = true;
        AppState.StringValue = "Testing 123...";
    }
}

Все работает как положено и пункт меню отображается так же, как и строковое значение в теге H2.

Однако, если я добавлю вызов моей функции Javasript через JSRuntime, например:

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

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

Запускается javascript, и значения AppState обновляются (я добавил журнал консоли, чтобы проверить это) НО навигация не обновляется, чтобы показать элемент меню, а тег H2 не показывает новое строковое значение ...

Если вы перейдете на страницу в меню, состояние обновится, но этого не произойдет снова, если вы выполните жесткое обновление sh ....

Я попытался добавить StateHasChanged ( ) вызов, но это не сработало:

@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!
    }
}

Поскольку состояние, казалось, обновлялось после навигации, я также попытался принудительно запустить канун навигации nt, тоже безрезультатно:

@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);
    }
}

Я продолжаю пробовать еще несколько вещей, но, похоже, ничего не работает. StateHasChanged () действительно должно было быть исправлением для чего-то вроде этого в моем понимании. Что-нибудь еще, что я должен попробовать?

UPDATE

Как указано в enet, мне нужно уведомить нижестоящие компоненты, когда состояние изменилось. Проблема была вызвана не JSRuntime, а задержкой, которую он создал в App.razor, в результате чего состояние объекта при аппаратном обновлении sh казалось другим. В первом сценарии объект был обновлен ДО того, как другие компоненты начали рендеринг, поэтому они использовали уже обновленное состояние. Но как только возникала какая-либо задержка (например, вызов JSRuntime), им требовалось уведомление, чтобы обновить свое состояние, потому что у них была возможность выполнить рендеринг до задержки с использованием более старого состояния. Я заменил вызов JSRuntime на Task.Delay () на несколько миллисекунд, и это привело к той же проблеме! Это разрешило добавление триггера к событию.

1 Ответ

2 голосов
/ 08 мая 2020

MessageService.cs

 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;
}

Примечание. Служба является обычным классом ... Он предоставляет службы другим объектам, и для этого необходимо добавить его в контейнер DI в методе Startup.ConfigureServices. доступны для запрашивающих клиентов. Добавьте это: в метод ConfigureServices:

 services.AddScoped<MessageService>();

Примечание. Как вы можете видеть, я определяю делегат события типа Action, который вызывается из метода доступа set свойства, когда пользователь вводит текст в текст поле в Component3. При запуске этого делегата текст, введенный Components3, будет отображаться в компоненте Index, который является родительским для Component2 (см. Код ниже).

Index.razor

  @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);
    }


 }
}

Обратите внимание, что мы напрямую привязать к свойству MessageService.Message, но метод StateHasChanged должен быть вызван для обновления sh отображения текста.

Component2.razor

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

@code {

}

Component3.razor

 @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();
        });

     }
 }

Обратите внимание, что в Component3 мы привязываем MessageService.Message к текстовому полю, и привязка происходит каждый раз, когда вы нажимаете на клавиатуру (событие ввода по сравнению с событием изменения).

Вот и все, надеюсь, что это поможет, и не стесняйтесь задавать любые вопросы.

...