0

0

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

DDD

DDD

发布时间:2025-10-20 09:46:38

|

918人浏览过

|

来源于php中文网

原创

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会自动将其转换为前端的PromisegoryDataList[]>。

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 模板):


    

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

解决方案步骤

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

1. 修正 vaadin-grid.items 绑定

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

错误代码:

正确代码:


    

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

2. 修正 CategoryListRegisterViewStore.loadProductCategory 方法

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

Removal.AI
Removal.AI

AI移出图片背景工具

下载

错误代码:

loadProductCategory() {
    const prodCategory = appStore.tricampCrmProductStore.fetchProductCatgeory();
    runInAction(() => {
        this.categoryList = prodCategory; // prodCategory 此时是 Promise
    });
}

这里的问题在于,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 {
        return ProductEndpoint.fetchAllProdCategory();
    }

    // 或者,如果确实需要 await 并在内部处理,但通常不建议在这里使用 runInAction
    // async fetchProductCatgeory(): Promise {
    //     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 {
        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 视图组件):


    
    
    
    

注意事项与最佳实践

  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应用。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

832

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

737

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

734

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16925

2023.08.03

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.2万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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