Microsoft MAUI Blazor WebAssembly移动端项目如何按需按页面加载不同JS脚本?
MAUI Blazor WebAssembly 按页面按需加载JS脚本方案
方案1:使用ES模块+组件生命周期动态导入
这是最推荐的方式,利用ES模块特性,配合Blazor的IJSObjectReference管理脚本,避免全局污染,还能自动清理资源。
步骤:
- 在
wwwroot下创建页面专属的ES模块脚本,比如wwwroot/Pages/ProductPage.js:
// ProductPage.js - 页面专属脚本,导出方法 export function initProductPage() { console.log("商品页面脚本初始化完成"); // 这里写页面专属的JS逻辑,比如绑定事件、初始化第三方插件等 } export function handleProductClick(productId) { console.log(`点击了商品:${productId}`); }
- 在对应的Razor页面(比如
ProductPage.razor)中,通过组件生命周期动态导入并调用:
@page "/product" @inject IJSRuntime JSRuntime @implements IAsyncDisposable <h3>商品页面</h3> <button @onclick="() => CallProductMethod(123)">点击测试JS方法</button> @code { private IJSObjectReference? _pageScriptModule; protected override async Task OnAfterRenderAsync(bool firstRender) { // 仅在首次渲染时加载脚本 if (firstRender && _pageScriptModule == null) { // 导入ES模块 _pageScriptModule = await JSRuntime.InvokeAsync<IJSObjectReference>( "import", "./Pages/ProductPage.js"); // 调用脚本中的初始化方法 await _pageScriptModule.InvokeVoidAsync("initProductPage"); } } private async Task CallProductMethod(int productId) { if (_pageScriptModule != null) { // 调用脚本中的业务方法 await _pageScriptModule.InvokeVoidAsync("handleProductClick", productId); } } // 页面销毁时清理脚本模块,避免内存泄漏 public async ValueTask DisposeAsync() { if (_pageScriptModule != null) { await _pageScriptModule.DisposeAsync(); } } }
方案2:自定义脚本加载服务统一管理
如果多个页面需要统一的脚本加载逻辑,可以封装一个服务来管理已加载的脚本,避免重复加载。
步骤:
- 创建脚本加载服务
ScriptLoaderService.cs:
using Microsoft.JSInterop; using System.Collections.Generic; namespace YourAppName.Services; public class ScriptLoaderService { private readonly IJSRuntime _jsRuntime; private readonly HashSet<string> _loadedScriptPaths = new(); public ScriptLoaderService(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } /// <summary> /// 加载指定路径的ES模块脚本,避免重复加载 /// </summary> public async Task<IJSObjectReference> LoadModuleAsync(string scriptPath) { if (_loadedScriptPaths.Contains(scriptPath)) { return await _jsRuntime.InvokeAsync<IJSObjectReference>("import", scriptPath); } var module = await _jsRuntime.InvokeAsync<IJSObjectReference>("import", scriptPath); _loadedScriptPaths.Add(scriptPath); return module; } /// <summary> /// 标记脚本为已卸载(清理记录) /// </summary> public void UnloadScript(string scriptPath) { _loadedScriptPaths.Remove(scriptPath); } }
- 在
Program.cs中注册服务:
builder.Services.AddScoped<ScriptLoaderService>();
- 在Razor页面中使用服务:
@page "/cart" @inject YourAppName.Services.ScriptLoaderService ScriptLoader @implements IAsyncDisposable <h3>购物车页面</h3> @code { private IJSObjectReference? _cartScriptModule; private const string _scriptPath = "./Pages/CartPage.js"; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _cartScriptModule = await ScriptLoader.LoadModuleAsync(_scriptPath); await _cartScriptModule.InvokeVoidAsync("initCartPage"); } } public async ValueTask DisposeAsync() { if (_cartScriptModule != null) { await _cartScriptModule.DisposeAsync(); ScriptLoader.UnloadScript(_scriptPath); } } }
方案3:动态创建Script标签加载传统脚本
如果需要加载非模块化的传统JS脚本(比如依赖全局变量的第三方库),可以通过JS动态创建script标签实现。
步骤:
- 在
wwwroot/js下创建通用脚本加载工具scriptLoader.js,并在wwwroot/index.html中引入这个工具:
<!-- index.html 中添加 --> <script src="js/scriptLoader.js"></script>
scriptLoader.js内容:
window.scriptLoader = { _loadedScripts: {}, /** * 加载指定URL的脚本 * @param {string} url 脚本路径 * @param {Function} callback 加载完成后的回调 */ load: function(url, callback) { if (this._loadedScripts[url]) { callback?.(); return; } const script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; script.onload = () => { this._loadedScripts[url] = true; callback?.(); }; document.head.appendChild(script); }, /** * 卸载指定URL的脚本(移除DOM节点并清理记录) * @param {string} url 脚本路径 */ unload: function(url) { const script = document.querySelector(`script[src="${url}"]`); if (script) { document.head.removeChild(script); delete this._loadedScripts[url]; // 可选:清理脚本创建的全局变量 if (window.cartPageUtils) { window.cartPageUtils = null; } } } };
- 在Razor页面中调用工具加载脚本:
@page "/checkout" @inject IJSRuntime JSRuntime @implements IAsyncDisposable <h3>结算页面</h3> @code { private const string _scriptUrl = "./Pages/CheckoutPage.js"; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // 加载脚本并执行初始化 await JSRuntime.InvokeVoidAsync("scriptLoader.load", _scriptUrl, async () => { await JSRuntime.InvokeVoidAsync("initCheckoutPage"); }); } } public async ValueTask DisposeAsync() { // 页面销毁时卸载脚本 await JSRuntime.InvokeVoidAsync("scriptLoader.unload", _scriptUrl); } }
关键注意事项:
- 所有方案都要实现
IAsyncDisposable接口,在页面销毁时清理脚本资源,避免内存泄漏。 - ES模块方式优先,因为它天然支持模块化,不会污染全局作用域,Blazor的
IJSObjectReference能很好地管理其生命周期。 - 如果脚本依赖第三方库,确保第三方库已提前加载(可在
index.html中全局引入,或在页面脚本中通过动态导入加载)。
内容的提问来源于stack exchange,提问作者Mariusz
相关产品推荐
相关产品推荐

