构建大型的复杂画布应用

文档本节的大多数文章介绍使用应用的用户所体验到的应用的运行时性能。 本文介绍制作应用程序的用户所体验到的应用性能。

随着应用变得更加庞大和复杂,Power Apps Studio 需要加载和管理大量控件、公式和数据源,相互依赖性全部呈指数级增长。 Power Apps Studio 的加载时间可能会更长,IntelliSense 和颜色编码等功能可能会出现延迟。 使用后面的建议可以更好地处理 Power Apps Studio 中的大型的复杂应用。 它们还可以帮助改进应用的运行时性能。

本文中的示例使用医院应急响应示例解决方案

使用 App.Formulas 代替 App.OnStart

提示

您可以将 With 函数和画布组件自定义输出属性用作命名公式的替代方法。

减少 Power Apps Studio 和应用加载时间的最佳方法是将 App.OnStart 中的变量和集合初始化替换为 App.Formulas 中的命名公式

我们来看看下面的示例,它使用 App.OnStart。

// Get the color of text on a dark background.
Set(varColorOnDark,RGBA(0, 0, 0, 1));

// Get the color of the menu icons.
Set(varColorMenuIcon,"#0070a9");

// Get the styles for a form.
Set(varFormStyle,
    {
        DataCard: { Height: 50 },
        Title: { Height: 50, Size: 21, Color: varColorOnDark },
        Control: { Height: 50, Size: 18 },
        Label: { Size: 18, Color: varColorOnDark }
    }
);

ClearCollect(
    FacilitiesList,
    ForAll(
        Facilities,
        { Name: 'Facility Name', Id: Facility }
    )
);
If(
    Not IsBlank(Param("FacilityID")),
    Set(ParamFacility,
        LookUp(
            FacilitiesList,
            Id = GUID(Param("FacilityID"))
        ).Name
    );
);

因为它们是一个语句序列,所以您的应用必须按顺序评估这些 SetCollect 调用,然后才能显示第一个屏幕,这会使应用加载更慢。 由于在返回最终结果之前,必须将整个 App.OnStart 视为一个整体,保留顺序,并聚合错误,因此公式对于 Power Apps Studio 来说分析起来很复杂。

有一个更好的方法。 使用 App.Formulas,并将这些变量和集合定义为命名公式,如以下示例所示。

// Get the color of text on a dark background.
varColorOnDark = RGBA(0, 0, 0, 1);

// Get the color of the menu icons.
varColorMenuIcon = "#0070a9";

// Get the styles for a form.
varFormStyle = 
    {
        DataCard: { Height: 50 },
        Title: { Height: 50, Size: 21, Color: varColorOnDark },
        Control: { Height: 50, Size: 18 },
        Label: { Size: 18, Color: varColorOnDark }
    };

FacilitiesList =
    ForAll(
        Facilities,
        { Name: 'Facility Name', Id: Facility }
    );

ParamFacility = 
    If( Not IsBlank(Param("FacilityID")),
        LookUp(
            FacilitiesList,
            Id = GUID(Param("FacilityID"))
        ).Name,
        Blank()
    );

这一更改可能看起来很小,但可能会产生巨大影响。 由于每个命名公式都独立于其他公式,Power Apps Studio 可以独立分析它们,从而有效地将一个大型的 App.OnStart 拆分为更小的部分。 仅凭这一变化,我们就看到 Power Apps Studio 加载时间下降了 80%。

您的应用加载速度也更快,因为在需要结果之前,它不必计算这些公式。 应用的第一个屏幕会立即显示。

命名公式不能在所有情况下都使用,因为您不能修改它们或将它们与 Set 一起使用。 有些情况下需要使用可以修改的状态变量 Set 非常适合这些情况,您应该继续使用它。 但是,通常情况下,您在 OnStart 中使用全局变量来设置不会改变的静态值。 在这些情况下,命名公式是更好的选择。

由于命名公式是不可变的,因此前缀 var(“variable”的缩写)作为命名约定不再适合。 我们没有更改本示例中的名称,因为这需要更改应用的其余部分才能匹配。

App.OnStart 中放置一个命名公式很有吸引力,但不要这样做。 它们不属于那里。 作为行为的属性,App.OnStart 按顺序计算它的每个语句,创建全局变量,并在应用加载时仅与数据库通信一次。 命名公式是定义如何在需要时进行某些计算的公式,始终为 true。 正是这种公式性质使它们能够独立,并允许应用在计算它们之前完成加载。

拆分长公式

App.OnStart 是长公式最严重的违规者之一,也是您应该开始的地方,但这不是它的唯一用途。

我们的研究表明,几乎所有 Power Apps Studio 加载时间长的应用都至少有一个超过 256,000 个字符的公式。 一些加载时间最长的应用的公式超过 100 万个字符。 长期以来给 Power Apps Studio 带来很大压力的公式。

更糟糕的是,复制和粘贴带有长公式的控件会复制控件属性中的公式,而不会实现。 Power Apps 以 Excel 为模型,其中的一个公式有多个副本很常见。 但是,在 Excel 中,公式仅限于一个表达式,上限为 8,000 个字符。 引入命令逻辑和链接运算符(;;;,具体取决于区域设置)后,Power Apps 公式可能变得更长。

一般的解决方案是将长公式拆分为较小的部分,并重复使用这些部分,正如我们在上一节中将 App.OnStart 中的 Set/Collect 语句更改为 App.Formulas 中的命名公式时所做的那样。 在其他编程语言中,可重用部分通常被称为子例程或用户定义函数。 您可以将命名公式视为用户定义函数的一种简单形式,没有参数或副作用。

随处使用命名公式

在前面的示例中,我们使用命名公式来替换 App.OnStart。 不过,您可以使用它们来替换应用中任何位置的计算。

例如,医院应急响应示例解决方案中的一个屏幕在 Screen.OnVisible 中包含此逻辑:

ClearCollect(
    MySplashSelectionsCollection,
    {
        MySystemCol: First(
            Filter(
                Regions,
                Region = MyParamRegion
            )
        ).System.'System Name',
        MyRegionCol: First(
            Filter(
                Regions,
                Region = MyParamRegion
            )
        ).'Region Name',
        MyFacilityCol: ParamFacility,
          MyFacilityColID:  LookUp(
            FacilitiesList,
            Id = GUID(Param("FacilityID"))
        ).Id
    }
); 

此公式可以拆分为一组命名公式。 它还让公式更易读。

MyRegion = LookUp(
                    Regions,
                    Region = MyParamRegion
           );

MyFacility = LookUp(
                    FacilitiesList,
                    Id = GUID(Param("FacilityID")
            );

MySplashSelectionsCollection = 
    {
        MySystemCol: MyRegion.System.'System Name',
        MyRegionCol: MyRegion.'Region Name',
        MyFacilityCol: ParamFacility,
        MyFacilityColID:  MyFacility.Id
    };

之前,在我们将 App.OnStart 中的大部分 Set 调用移动到 App.Formulas 中的命名公式时,我们将 ParamFacility 提取为命名公式。

仅当需要命名公式的值时,才会对其进行计算。 如果最初使用 Screen.OnVisible 是为了将工作延迟到屏幕显示,工作仍将作为 App.Formulas 中的全局命名公式延迟。

使用 With 函数

您也可以在公式中使用 With 函数来拆分逻辑。 在第一个参数中创建一个记录,其中包含您要用作字段的值,然后在第二个参数中使用这些字段来计算 With 的返回值。 例如,上面的示例可以编写为一个命名公式:

MySplashSelectionsCollection = 
    With( { MyRegion: LookUp(
                            Regions,
                            Region = MyParamRegion
                      ),
            MyFacility: LookUp(
                            FacilitiesList,
                            Id = GUID(Param("FacilityID")
                      ) 
           },
           {
                MySystemCol: MyRegion.System.'System Name',
                MyRegionCol: MyRegion.'Region Name',
                MyFacilityCol: ParamFacility,
                MyFacilityColID:  MyFacility.Id
           }
    )

这样使用 With 的一个缺点是 MyFacility 不能使用 MyRegion,因为它们是在同一 With 函数中定义的,命名公式则不存在此问题。 一个解决方案是嵌套 With 函数,并使用 As 关键字为每个函数命名记录,以便轻松访问所有 With 变量。

使用画布组件

画布组件最常用于创建 UI 控件,该控件可以像普通控件一样放在画布上。 您还可以使用它们而不将其放在 UI 中,来使用自定义输出属性执行计算,作为命名公式的替代。 画布组件可通过组件库轻松在应用之间共享,与命名公式不同,它们完全受支持。 但是,它们比命名公式更难配置和使用。

要拆分逻辑:

  1. 在 Power Apps Studio 中,在树视图上切换到组件选项卡。
  2. 创建新组件。
  3. 属性窗格中,打开访问应用范围
  4. 添加自定义属性。
  5. 根据需要,将属性类型设置为输出数据类型
  6. 选择创建
  7. 在屏幕顶部公式栏旁边的属性选取器中,选择新属性。
  8. 编写逻辑拆分和重用的公式。

要使用逻辑:

  1. 树视图上切换到屏幕选项卡。
  2. 插入窗格中,展开自定义,插入您的组件。
  3. 要使用属性计算值,使用 ComponentName.PropertyName

对命令性逻辑使用带有隐藏控件的 Select

命令性逻辑用于使用 SetCollect 修改状态,使用 Notify 通知用户,使用 NavigateLaunch 导航到另一个屏幕或应用,以及使用 PatchSubmitFormRemoveIf 将值写入数据库。

命名公式和画布组件自定义输出属性不支持命令性逻辑。 拆分命令性逻辑的一个常见方法是使用隐藏控件的 OnSelect 属性。

  1. 向屏幕添加 按钮 控件。
  2. OnSelect 属性设置为要执行的命令性逻辑。
  3. Visible 属性设置为 false,因为用户不需要查看或与之交互。
  4. 当您想要执行命令性逻辑时,调用 Select( Button )

例如,我们示例中的一个屏幕在 按钮 控件上具有以下 OnSelect 属性。 (这个简单的示例仅用于说明目的。 通常情况下,您只能对较长的公式使用此技巧。)

btnAction_17.OnSelect = 
    Trace("Feedback Screen: Submit Button",TraceSeverity.Information);
    If(
        // Proceed if all forms are validated.
        And(
            FormFeedback.Valid
        ),
    
        // Set the updates to static variables.
        Set(updatesFeedback,Patch(Defaults('App Feedbacks'), FormFeedback.Updates));
        // Submit the first form. Subsequent actions can be found in the OnSuccess.
        SubmitForm(FormFeedback);
        ,
    
        Notify("Please complete all fields before proceeding",
               NotificationType.Warning,2000)
    );

要将此逻辑拆分为多个部分,我们可以将各个部分放到单独的 按钮 控件上,然后从原始逻辑中选择部分:

btnTrace.OnSelect = 
    Trace("Feedback Screen: Submit Button",TraceSeverity.Information);

btnSubmit.OnSelect = 
    If(
        // Proceed if all forms are validated.
        And(
            FormFeedback.Valid
        ),
    
        // Set the updates to static variables.
        Set(updatesFeedback,Patch(Defaults('App Feedbacks'), FormFeedback.Updates));
        // Submit the first form. Subsequent actions can be found in OnSuccess.
        SubmitForm(FormFeedback);
        ,
    
        Notify("Please complete all fields before proceeding",
               NotificationType.Warning,2000)
    );

btnAction_17.OnSelect = 
    Select( btnTrace );
    Select( btnSubmit );

此技巧仅在同一屏幕上有效。 其他稍微复杂一点的技巧可以跨屏幕使用,如使用 切换 控件,将 OnCheck 设置为要运行的逻辑,将默认值设置为全局变量,然后在要运行逻辑的点使用 Set( global, true ); Set( global, false ) 切换全局变量。

在此示例中,已执行某些逻辑拆分。 注释中提到“后续操作可以在 OnSuccess 中找到。” 此事件在成功提交记录后运行命令性逻辑,这是特定于 SubmitForm 函数的解决方案。

对应用进行分区

有些应用会增加到数千个控件、数百个数据源,这会减慢 Power Apps Studio 的速度。 与长公式一样,大型应用可以分成更小的部分,这些部分一起工作来创建一个用户体验。

单独的画布应用

一种方法是在单独的画布应用中实现各个部分,使用 Launch 函数在单独的应用之间导航和传递所需的上下文。

此方法被用于医院应急响应示例解决方案。 单独的应用管理整个应用的每个主要区域。 这些应用通过每个应用在启动屏幕上显示的组件库共享一个通用的交换台组件:

医院应急响应示例解决方案画布应用在手机上运行的屏幕截图,显示交换台画布组件。

当用户选择一个区域时,该组件将使用有关可用应用以及哪个应用托管组件的元数据。 如果所需屏幕在此应用中(即 ThisItem.Screen 不为空),将进行 Navigate 调用。 但是,如果所需的屏幕位于不同的应用中(即 ThisItem.PowerAppID 不为空),Launch 函数将与目标的应用 ID 和 FacilityID 上下文一起使用:

If(
    IsBlank(ThisItem.Screen),
    If(IsBlank(ThisItem.PowerAppID), 
        Launch(ThisItem.URL),           
        Launch("/providers/Microsoft.PowerApps/apps/" & ThisItem.PowerAppID, 
               "FacilityID", Home_Facility_DD.Selected.Id)
    ),
    Navigate(
        ThisItem.Screen,
        Fade
    )
);

当启动另一个应用时,原始应用中的状态将丢失。 在调用 Launch 函数之前,请确保保存任何状态。 将其写入数据库,调用 SaveData,或使用通过 Param 函数读取的参数将状态传递到目标应用。

带有自定义页面的模型驱动应用

部分也可以作为自定义页面实现。 自定义页面充当一个迷你画布应用,带有一个用于导航的模型驱动应用容器。

备注

您能告诉我们您的文档语言首选项吗? 进行简短调查。(请注意,此调查是英文版调查)

此调查大约需要七分钟。 不会收集个人数据(隐私声明)。