You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor页面跳转返回后调用StateHasChanged不刷新动态加载内容问题

问题:Blazor页面跳转返回后滚动加载UI不刷新

现有实现代码

JavaScript 滚动监听代码

let lock = false;

window.setupScrollListener = (objRef) => {
  window.addEventListener("scroll", () => {
    if (lock) {
      return;
    }

    const scrollTop =
      document.documentElement.scrollTop || document.body.scrollTop;
    const windowHeight =
      document.documentElement.clientHeight || document.body.clientHeight;
    const scrollHeight =
      document.documentElement.scrollHeight || document.body.scrollHeight;

    let delta = Math.abs(scrollTop + windowHeight - scrollHeight);

    if (delta > 0 && delta < 100) {
      lock = true;

      setTimeout(() => {
        objRef.invokeMethodAsync("LoadMoreData")
          .then((result) => (lock = false))
          .catch((error) => console.error(error));
      }, 500);
    }
  });
};

Blazor 页面代码

@page "/"
@using XMagic.Client.Shared
@rendermode @(new InteractiveAutoRenderMode(prerender: false))

<a href="/">index</a>
<a href="/test">test</a>

<div class="flex mx-auto">
    <div class="flex flex-col gap-4">
        @foreach (var img in _col1)
        {
            <Image Src="@img"/>
        }
    </div>
</div>

@code {
    List<string> _col1 = ["images/01.jpeg", "images/banner1.jpeg", "images/01.jpeg"];

    [Inject] IJSRuntime? JsRuntime { get; set; } = default!;
    DotNetObjectReference<Test>? _objRef;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _objRef = DotNetObjectReference.Create(this);
            if (JsRuntime != null)
            {
                await JsRuntime.InvokeVoidAsync("setupScrollListener", _objRef);
            }
        }
    }

    [JSInvokable]
    public bool LoadMoreData()
    {
        _col1.Add("images/01.jpeg");
        _col1.Add("images/banner1.jpeg");
        _col1.Add("images/01.jpeg");
        
        Console.WriteLine("LoadMoreData Exec...");
        Console.WriteLine(_col1.Count);
        
        StateHasChanged();

        return true;
    }

    public void Dispose() => _objRef?.Dispose();
}

问题场景

  • 首次通过地址栏访问首页,滚动到底部,LoadMoreData正常调用,UI同步更新。
  • 跳转至test页面后返回首页,滚动到底部仍能触发LoadMoreData,但页面内容不再更新。

问题原因

路由跳转返回首页时,之前注册的scroll事件仍持有旧组件实例的DotNetObjectReference,调用LoadMoreData时数据确实更新,但对应的组件实例已经不是当前渲染的实例,因此StateHasChanged()无法触发当前UI的刷新。另外,重复注册scroll事件还会导致多次触发的问题。

解决方案

修改后的JavaScript代码

let lock = false;
let scrollHandler = null;

window.setupScrollListener = (objRef) => {
  // 先移除旧监听,避免重复注册
  if (scrollHandler) {
    window.removeEventListener("scroll", scrollHandler);
  }

  scrollHandler = () => {
    if (lock) {
      return;
    }

    const scrollTop =
      document.documentElement.scrollTop || document.body.scrollTop;
    const windowHeight =
      document.documentElement.clientHeight || document.body.clientHeight;
    const scrollHeight =
      document.documentElement.scrollHeight || document.body.scrollHeight;

    let delta = Math.abs(scrollTop + windowHeight - scrollHeight);

    if (delta > 0 && delta < 100) {
      lock = true;

      setTimeout(() => {
        objRef.invokeMethodAsync("LoadMoreData")
          .then((result) => (lock = false))
          .catch((error) => console.error(error));
      }, 500);
    }
  };

  window.addEventListener("scroll", scrollHandler);
};

window.removeScrollListener = () => {
  if (scrollHandler) {
    window.removeEventListener("scroll", scrollHandler);
    scrollHandler = null;
  }
};

修改后的Blazor页面代码

@page "/"
@using XMagic.Client.Shared
@rendermode @(new InteractiveAutoRenderMode(prerender: false))
@implements IDisposable

<a href="/">index</a>
<a href="/test">test</a>

<div class="flex mx-auto">
    <div class="flex flex-col gap-4">
        @foreach (var img in _col1)
        {
            <Image Src="@img"/>
        }
    </div>
</div>

@code {
    List<string> _col1 = ["images/01.jpeg", "images/banner1.jpeg", "images/01.jpeg"];

    [Inject] IJSRuntime? JsRuntime { get; set; } = default!;
    DotNetObjectReference<Index>? _objRef; // 修正为当前组件类型

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        // 每次渲染都清理旧监听,重新绑定当前组件实例
        if (JsRuntime != null)
        {
            await JsRuntime.InvokeVoidAsync("removeScrollListener");
            
            _objRef?.Dispose();
            _objRef = DotNetObjectReference.Create(this);
            await JsRuntime.InvokeVoidAsync("setupScrollListener", _objRef);
        }
    }

    [JSInvokable]
    public bool LoadMoreData()
    {
        _col1.Add("images/01.jpeg");
        _col1.Add("images/banner1.jpeg");
        _col1.Add("images/01.jpeg");
        
        Console.WriteLine("LoadMoreData Exec...");
        Console.WriteLine(_col1.Count);
        
        StateHasChanged();

        return true;
    }

    public void Dispose() 
    {
        _objRef?.Dispose();
        // 组件销毁时移除监听
        _ = JsRuntime?.InvokeVoidAsync("removeScrollListener");
    }
}

关键调整说明

  • 移除旧监听:在注册新的scroll事件前,先移除之前的监听,避免旧实例的回调继续生效。
  • 重新绑定当前实例:每次组件渲染时(包括返回首页后的非首次渲染),都创建新的DotNetObjectReference绑定当前组件实例,确保JS调用的是当前活跃的组件。
  • 组件销毁清理:在Dispose方法中调用JS移除监听,同时释放DotNetObjectReference,避免内存泄漏。
  • 修正类型匹配:将DotNetObjectReference<Test>改为当前组件的类型(比如Index),确保类型正确匹配。

内容的提问来源于stack exchange,提问作者boolean0011

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 01:05:55