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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 04:18:24