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

Blazor同一组件多实例EventCallback错误触发问题

Blazor多组件实例JS互调回调错乱问题修复

问题根因

你的实现存在3个核心错误,和EventCallback参数覆盖无关:

  • PlaceToUse被声明为static静态属性:静态属性属于组件类全局共享,所有AddressComponent实例共用同一个Place对象,多实例下必然出现数据互相覆盖。
  • JS侧引用存储错误:你的initAutocomplete方法大概率用单个全局变量存储传入的DotNet对象引用,后渲染的组件(PageB中的实例)会直接覆盖前一个实例存储的引用,导致所有输入框触发事件时,都调用最后一个实例的回调方法。
  • 缺少生命周期管理:创建DotNetObjectReference后既没有持有引用,也没有在组件销毁时主动释放,同时JS侧绑定的事件没有随组件销毁解绑,既会造成内存泄漏,也会加剧回调错乱问题。
    额外说明:可复用子组件不需要加@page路由特性,该特性仅用于可直接通过URL访问的页面组件。

修复步骤

  • 移除PlaceToUse的static修饰符,改为实例私有成员,每个组件实例独立持有自己的状态数据。
  • 持有DotNetObjectReference实例,组件实现IAsyncDisposable接口,在销毁逻辑中主动释放引用、通知JS侧解绑对应事件。
  • 调整JS侧逻辑,用Map结构按输入框ID维度存储每个组件实例对应的DotNet引用和事件监听器,避免全局变量覆盖。

修复后的AddressComponent代码

@inject IJSRuntime JSRuntime
@implements IAsyncDisposable

<input id="@Id" class="form-control" name="address" placeholder="Search address" />

@code {
    // 改为实例私有属性,每个组件实例独立持有Place对象
    private Place PlaceToUse { get; set; } = new Place();
    // 持有DotNet对象引用,用于后续释放
    private DotNetObjectReference<AddressComponent>? _dotNetRef;

    [Parameter]
    public EventCallback<Place> OnPlaceSelected { get; set; }

    [Parameter]
    // 给参数设置默认值,避免未传参时出现空引用
    public string Id { get; set; } = string.Empty;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _dotNetRef = DotNetObjectReference.Create(this);
            await JSRuntime.InvokeVoidAsync("initAutocomplete", _dotNetRef, Id);
        }
    }

    [JSInvokable]
    public void UpdateLocalizationData(string lat, string lon)
    {
        // 每次回调创建新的Place实例,避免引用复用导致后续修改污染已传递的数据
        PlaceToUse = new Place
        {
            Id = Guid.NewGuid(),
            Latitudine = lat,
            Longitudine = lon
        };

        _ = OnPlaceSelected.InvokeAsync(PlaceToUse);
    }

    public async ValueTask DisposeAsync()
    {
        // 通知JS侧销毁当前实例绑定的事件、清理引用
        await JSRuntime.InvokeVoidAsync("destroyAutocomplete", Id);
        // 释放DotNet对象引用
        _dotNetRef?.Dispose();
    }
}

修复后的JS侧代码

// 用Map按输入框ID存储对应实例的引用和事件监听器,避免全局覆盖
const autocompleteInstanceMap = new Map();

window.initAutocomplete = function(dotNetRef, elementId) {
    const inputEl = document.getElementById(elementId);
    if (!inputEl) return;

    // 省略原有的地址自动补全初始化逻辑
    const autocomplete = new google.maps.places.Autocomplete(inputEl);
    
    // 绑定place_changed事件
    const placeChangeListener = autocomplete.addListener('place_changed', () => {
        const place = autocomplete.getPlace();
        // 省略经纬度提取逻辑
        const lat = place.geometry.location.lat().toString();
        const lon = place.geometry.location.lng().toString();
        // 调用对应组件实例的回调方法
        dotNetRef.invokeMethodAsync('UpdateLocalizationData', lat, lon);
    });

    // 将当前实例的相关引用存入Map
    autocompleteInstanceMap.set(elementId, {
        dotNetRef: dotNetRef,
        autocomplete: autocomplete,
        listener: placeChangeListener
    });
}

window.destroyAutocomplete = function(elementId) {
    if (!autocompleteInstanceMap.has(elementId)) return;
    const instance = autocompleteInstanceMap.get(elementId);
    // 解绑事件监听器
    google.maps.event.removeListener(instance.listener);
    // 释放DotNet对象引用
    instance.dotNetRef.dispose();
    // 移除Map中存储的实例数据
    autocompleteInstanceMap.delete(elementId);
}

设计问题说明

  • Blazor组件的实例级状态禁止用静态成员存储:静态成员归属于类本身,而非类的实例,只要页面上同时存在多个同类型组件,必然出现状态互相污染、回调错乱的问题。
  • JS互操作必须做生命周期对齐:C#侧创建的DotNetObjectReference、JS侧绑定的DOM事件都需要在组件销毁时主动清理,否则会造成内存泄漏,且已销毁组件的引用残留会导致非预期的回调触发。
  • 组件职责要清晰:@page特性仅用于路由页面,可复用的嵌套子组件不需要声明路由,避免组件既可以作为子组件嵌套使用,又可以被URL直接访问导致非预期渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:15:32