首页 > Java > java教程 > 正文

Hilla/Vaadin Grid数据绑定与异步数据处理深度解析

DDD
发布: 2025-10-20 09:46:38
原创
913人浏览过

Hilla/Vaadin Grid数据绑定与异步数据处理深度解析

本文深入探讨了在hilla/vaadin应用中,使用`vaadin-grid`绑定异步数据时常见的promise类型错误及其解决方案。核心问题在于对`vaadin-grid.items`属性的错误绑定、异步方法中`promise`的未正确解析以及mobx `runinaction`的误用。通过纠正数据绑定、合理运用`async/await`和理解mobx状态管理,确保数据能够正确加载并显示在grid中。

在Hilla/Vaadin框架中构建富客户端应用时,常常需要从后端服务获取数据并将其展示在vaadin-grid等UI组件中。由于数据获取通常是异步操作,若不正确处理JavaScript Promise和数据绑定机制,便可能遭遇“Type 'Promise' is missing the following properties from type”之类的类型错误。本教程将详细剖析此类问题,并提供一套健壮的解决方案。

理解问题根源

出现此类错误通常是以下几个方面的问题交织导致的:

  1. vaadin-grid.items属性的错误绑定:vaadin-grid的items属性期望接收一个可迭代的数据集合(如数组),而非一个异步方法或一个Promise对象。
  2. 异步数据未正确解析:从后端服务获取数据的方法通常返回一个Promise。在将数据赋值给状态变量之前,必须等待这个Promise解析并获取其最终值。
  3. MobX runInAction的误用:在MobX中,runInAction主要用于在异步操作中修改可观察状态,但不应将其用于封装异步方法的返回值,尤其当该方法本身旨在返回一个Promise时。

下面,我们将结合一个实际案例来逐一解决这些问题。

案例分析与解决方案

假设我们有一个Hilla后端服务,提供获取产品类别列表的端点,以及一个前端MobX Store来管理这些数据,并最终在vaadin-grid中展示。

原始问题代码概览

后端服务 (ProductEndpoint.java):

@Endpoint
@AnonymousAllowed
public class ProductEndpoint {
    // ... 其他方法 ...
    public @Nonnull List<@Nonnull ProductCategoryDataList> fetchAllProdCategory() {
        return this.productService.fetchProductCategory(""); // 返回一个List
    }
    // ...
}
登录后复制

后端端点返回的是一个List,Hilla会自动将其转换为前端的Promise<ProductCategoryDataList[]>。

MobX Store (ProductStore.ts):

export class ProductStore {
    constructor() {
        makeAutoObservable(this);
        this.fetchProductCatgeory(); // 构造时调用
    }

    async fetchProductCatgeory() {
        const prodCatData = await ProductEndpoint.fetchAllProdCategory();
        runInAction(() => {
            return prodCatData; // 问题点:在runInAction中返回,但外部没有接收
        });
    }
}
登录后复制

视图层Store (CategoryListRegisterViewStore.ts):

export class CategoryListRegisterViewStore {
    categoryList: ProductCategoryDataList[] = [];

    constructor() {
        makeAutoObservable(this, { categoryList: observable.shallow });
        this.loadProductCategory(); // 构造时加载数据
    }

    loadProductCategory() {
        const prodCategory = appStore.tricampCrmProductStore.fetchProductCatgeory(); // 问题点:prodCategory此时是一个Promise
        runInAction(() => {
            this.categoryList = prodCategory; // 问题点:将Promise赋值给了数组类型
        });
    }
}
登录后复制

UI组件 (HTML 模板):

<vaadin-grid theme="row-stripes" .items="${categoryListRegisterViewStore.loadProductCategory}" >
    <!-- ... 列定义 ... -->
</vaadin-grid>
登录后复制

问题点:items属性绑定到了一个方法,而非一个数据数组。

解决方案步骤

我们将从UI层向上层逐一修正。

1. 修正 vaadin-grid.items 绑定

vaadin-grid的items属性应该绑定到实际的数据数组,而不是加载数据的方法。数据加载方法负责更新这个数组,而Grid则直接从数组中获取数据。

错误代码:

<vaadin-grid theme="row-stripes" .items="${categoryListRegisterViewStore.loadProductCategory}" >
登录后复制

正确代码:

<vaadin-grid theme="row-stripes" .items="${categoryListRegisterViewStore.categoryList}" >
    <!-- ... 列定义 ... -->
</vaadin-grid>
登录后复制

现在,vaadin-grid将直接观察categoryListRegisterViewStore.categoryList数组的变化,并在数据更新时自动刷新。

2. 修正 CategoryListRegisterViewStore.loadProductCategory 方法

loadProductCategory方法负责触发数据加载,并等待异步操作完成后,将解析后的数据赋值给categoryList。

即构数智人
即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36
查看详情 即构数智人

错误代码:

loadProductCategory() {
    const prodCategory = appStore.tricampCrmProductStore.fetchProductCatgeory();
    runInAction(() => {
        this.categoryList = prodCategory; // prodCategory 此时是 Promise<ProductCategoryDataList[]>
    });
}
登录后复制

这里的问题在于,fetchProductCatgeory()返回的是一个Promise,而不是ProductCategoryDataList[]数组。直接将其赋值给this.categoryList会导致类型不匹配错误。我们需要使用await关键字等待Promise解析。

正确代码:

export class CategoryListRegisterViewStore {
    categoryList: ProductCategoryDataList[] = [];

    constructor() {
        makeAutoObservable(this, { categoryList: observable.shallow });
        this.loadProductCategory();
    }

    async loadProductCategory() { // 标记为 async
        const prodCategory = await appStore.tricampCrmProductStore.fetchProductCatgeory(); // 使用 await
        runInAction(() => {
            this.categoryList = prodCategory; // prodCategory 此时是解析后的数据数组
        });
    }
    // ...
}
登录后复制

通过将loadProductCategory标记为async并使用await,我们确保prodCategory变量接收到的是Promise解析后的实际数据数组。runInAction在这里的作用是确保对this.categoryList的赋值操作在MobX的action中进行,从而正确触发UI更新。

3. 修正 ProductStore.fetchProductCatgeory 方法

ProductStore中的fetchProductCatgeory方法旨在从Hilla端点获取数据并返回一个Promise。然而,原始代码在runInAction中返回数据,导致外部调用者无法正确获取到Promise。

错误代码:

async fetchProductCatgeory() {
    const prodCatData = await ProductEndpoint.fetchAllProdCategory();
    runInAction(() => {
        return prodCatData; // 这里的 return 仅对 runInAction 内部有效,不会成为 fetchProductCatgeory 的返回值
    });
}
登录后复制

async函数默认返回一个Promise。如果函数体内没有显式返回,或者在runInAction等回调中返回,其外部的Promise将解析为undefined或void。

正确代码:

export class ProductStore {
    constructor() {
        makeAutoObservable(this);
        // 可以在这里直接调用 fetchProductCatgeory,但需注意其返回 Promise
        // 如果需要在构造函数中等待数据,则构造函数也需为 async,或使用 .then()
    }

    // 推荐直接返回 Hilla 端点调用的 Promise
    fetchProductCatgeory(): Promise<ProductCategoryDataList[]> {
        return ProductEndpoint.fetchAllProdCategory();
    }

    // 或者,如果确实需要 await 并在内部处理,但通常不建议在这里使用 runInAction
    // async fetchProductCatgeory(): Promise<ProductCategoryDataList[]> {
    //     const prodCatData = await ProductEndpoint.fetchAllProdCategory();
    //     // 这里没有状态修改,所以不需要 runInAction
    //     return prodCatData;
    // }
}
登录后复制

最简洁且推荐的方式是让fetchProductCatgeory直接返回ProductEndpoint.fetchAllProdCategory()的Promise。这样,上层调用者(如CategoryListRegisterViewStore.loadProductCategory)就可以直接await这个Promise。

整合后的代码示例

以下是经过修正后的CategoryListRegisterViewStore和ProductStore以及vaadin-grid的绑定方式:

ProductStore.ts (简化版):

import { makeAutoObservable } from 'mobx';
import { ProductEndpoint } from 'Frontend/generated/endpoints'; // 假设路径
import { ProductCategoryDataList } from 'Frontend/generated/endpoints'; // 假设路径

export class ProductStore {
    constructor() {
        makeAutoObservable(this);
    }

    // 直接返回 Hilla 端点调用的 Promise
    fetchProductCatgeory(): Promise<ProductCategoryDataList[]> {
        return ProductEndpoint.fetchAllProdCategory();
    }

    // 其他方法...
}
登录后复制

CategoryListRegisterViewStore.ts:

import { makeAutoObservable, runInAction, observable } from 'mobx';
import { ProductCategoryDataList } from 'Frontend/generated/endpoints'; // 假设路径
import { appStore } from './AppStore'; // 假设 appStore 包含 ProductStore 实例

export class CategoryListRegisterViewStore {
    categoryList: ProductCategoryDataList[] = [];

    constructor() {
        makeAutoObservable(this, {
            categoryList: observable.shallow,
        });
        this.loadProductCategory(); // 在构造函数中触发数据加载
    }

    async loadProductCategory() {
        try {
            const prodCategory = await appStore.tricampCrmProductStore.fetchProductCatgeory();
            runInAction(() => {
                this.categoryList = prodCategory;
            });
        } catch (error) {
            console.error("Failed to load product categories:", error);
            // 可以在这里处理错误,例如显示错误消息
        }
    }

    // 其他方法...
}

// 导出 store 实例,供视图层使用
export const categoryListRegisterViewStore = new CategoryListRegisterViewStore();
登录后复制

HTML 模板 (或 LitElement/TypeScript 视图组件):

<vaadin-grid theme="row-stripes" .items="${categoryListRegisterViewStore.categoryList}" >
    <vaadin-grid-column header="Category Name" path="name"></vaadin-grid-column>
    <!-- 假设 ProductCategoryDataList 有一个 'name' 属性 -->
    <vaadin-grid-column header="Description" path="description"></vaadin-grid-column>
    <!-- ... 其他列定义 ... -->
</vaadin-grid>
登录后复制

注意事项与最佳实践

  1. 异步操作的链式调用:当一个异步操作的结果需要传递给另一个异步操作时,始终使用await或.then()来确保获取到的是解析后的值。
  2. MobX runInAction 的作用:runInAction主要用于在非Action函数(如异步回调)中修改可观察状态。它确保了状态修改是原子性的,并能正确触发MobX的响应式更新。不要在其中返回一个值作为外部async函数的返回值。
  3. 错误处理:在异步方法中加入try-catch块是良好的实践,以便捕获网络请求或数据处理过程中可能发生的错误,提升应用的健壮性。
  4. 加载状态:对于异步数据加载,建议在Store中添加一个isLoading的可观察属性。在数据加载开始时设置为true,加载完成(无论成功或失败)时设置为false。UI组件可以根据此属性显示加载指示器,提升用户体验。
  5. 数据类型匹配:始终确保将正确类型的数据赋值给相应的变量。TypeScript的类型检查在这里起到了关键作用,帮助我们在编译阶段发现潜在的类型不匹配问题。
  6. Hilla Endpoints的返回值:Hilla自动生成的TypeScript客户端方法会返回Promise。在使用这些方法时,要牢记这一点。

总结

在Hilla/Vaadin应用中处理异步数据和vaadin-grid数据绑定时,关键在于正确理解Promise的工作机制、async/await的用法以及MobX状态管理的原则。通过将vaadin-grid.items绑定到已解析的数据数组、在异步方法中正确使用await解析Promise,并避免runInAction的误用,可以有效避免常见的类型错误,确保数据能够顺畅地流动并正确展示在用户界面上。遵循这些最佳实践,将有助于构建更稳定、可维护的Hilla/Vaadin应用。

以上就是Hilla/Vaadin Grid数据绑定与异步数据处理深度解析的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号