MAUI Hybrid Android继承场景下JS Interop调用失败问题
问题:MAUI Blazor自定义组件作用域JS模块调用失败
我开发了面向Android和Windows的MAUI应用,为自定义组件使用作用域JS文件。所有自定义组件继承自ComponentBaseExtended(该类继承自ComponentBase),基类中实现了方法用于加载组件对应JS模块(若存在)。
基类中可成功加载模块,且能正常执行initModule、SwipeRight和moduleLoaded方法,调试日志正常输出。但子类SwipeItem.razor.cs调用SwipeRight方法时出现错误:
[chromium] Error: Could not find "adjustWidthOnSwipeRight" ("adjustWidthOnSwipeRight" was undefined)
相关代码
子类SwipeItem.razor.cs调用代码
if (swipeDirection == MudBlazor.SwipeDirection.LeftToRight && !_editDrawerOpen) { // from default to edit motionClass = "move-right"; deleteIconClass = "icon-hidden"; editIconClass = "icon-visible"; await JSRuntime.InvokeVoidAsync("SwipeRight"); }
作用域JS文件SwipeItem.razor.js
let isModuleLoaded = false; export function initModule() { console.log("initModule - Value of isModuleLoaded:", isModuleLoaded); isModuleLoaded = true; } export function SwipeRight() { console.log("adjustWidthOnSwipeRight - Value of swipeDistance:"); } export function moduleLoaded() { console.log("moduleLoaded - Value of isModuleLoaded:", isModuleLoaded); return isModuleLoaded; }
基类ComponentBaseExtended.razor.cs加载模块代码
protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { var type = typeof(TComponent); var namespaceParts = type.Namespace?.Split('.') ?? Array.Empty<string>(); var folderPath = string.Join("/", namespaceParts.Skip(1)); // Skip the root namespace var componentName = type.Name; var scriptPath = $"./{folderPath}/{componentName}.razor.js"; try { // Load the JS module var module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", scriptPath); await module.InvokeVoidAsync("initModule"); var isLoaded = await module.InvokeAsync<bool>("moduleLoaded"); if (!isLoaded) { } else { await module.InvokeVoidAsync("SwipeRight"); } } catch (JSException jsex) { // Handle the case where the script doesnt exist Console.WriteLine($"No JS module found for {componentName}, or there was an error in loading it."); } } }
原因分析
直接使用JSRuntime.InvokeVoidAsync("SwipeRight")是在全局作用域查找方法,但作用域JS模块的导出函数并未挂载到全局,仅存在于模块内部。基类调用成功是因为持有了IJSObjectReference实例,通过该实例调用模块内函数;子类未保留模块引用,直接全局调用自然找不到对应方法。
解决方案
1. 基类中保存模块引用
修改ComponentBaseExtended,添加受保护字段存储加载后的模块引用,并实现异步释放逻辑:
public class ComponentBaseExtended<TComponent> : ComponentBase where TComponent : ComponentBase { [Inject] protected IJSRuntime JSRuntime { get; set; } = default!; protected IJSObjectReference? ModuleReference { get; private set; } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { var type = typeof(TComponent); var namespaceParts = type.Namespace?.Split('.') ?? Array.Empty<string>(); var folderPath = string.Join("/", namespaceParts.Skip(1)); var componentName = type.Name; var scriptPath = $"./{folderPath}/{componentName}.razor.js"; try { ModuleReference = await JSRuntime.InvokeAsync<IJSObjectReference>("import", scriptPath); await ModuleReference.InvokeVoidAsync("initModule"); var isLoaded = await ModuleReference.InvokeAsync<bool>("moduleLoaded"); if (isLoaded) { await ModuleReference.InvokeVoidAsync("SwipeRight"); } } catch (JSException jsex) { Console.WriteLine($"No JS module found for {componentName}, or there was an error in loading it."); } } } // 组件销毁时释放模块引用 public async ValueTask DisposeAsync() { if (ModuleReference is not null) { await ModuleReference.DisposeAsync(); } } }
2. 子类通过模块引用调用函数
修改SwipeItem.razor.cs,通过基类的ModuleReference调用模块内的SwipeRight方法:
if (swipeDirection == MudBlazor.SwipeDirection.LeftToRight && !_editDrawerOpen) { motionClass = "move-right"; deleteIconClass = "icon-hidden"; editIconClass = "icon-visible"; if (ModuleReference is not null) { await ModuleReference.InvokeVoidAsync("SwipeRight"); } }
3. 子类实现异步释放接口
让SwipeItem继承IAsyncDisposable,调用基类的释放方法避免内存泄漏:
public partial class SwipeItem : ComponentBaseExtended<SwipeItem>, IAsyncDisposable { // 你的其他业务代码... public async ValueTask DisposeAsync() { await base.DisposeAsync(); GC.SuppressFinalize(this); } }
内容的提问来源于stack exchange,提问作者GeKu
相关产品推荐
相关产品推荐

