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
相关产品推荐
相关产品推荐

