通过


代码优化

随着画布应用的演进以满足不同的业务需求,保持性能优化至关重要。 数据处理、用户界面设计和应用功能都需要谨慎的代码优化方法。

随着画布应用变得更加复杂,可能会遇到数据检索、公式复杂性和呈现速度问题。 若要将强大的功能与响应式用户界面进行平衡,请使用系统方法进行代码优化。

Power Fx 公式优化

本部分提供优化 Power Fx 公式的最佳做法。

具有功能

With 函数计算单个记录的公式。 公式可以计算值或执行操作,例如修改数据或处理连接。 使用 With 将复杂公式拆分为较小的命名子公式,使其更易于阅读。 这些命名值类似于仅限于 With 作用域的简单局部变量。 With 优于上下文或全局变量,因为它自包含、易于理解,并且适用于任何声明性公式上下文。 详细了解 With 函数

使用 With 函数的 Power Fx 公式的屏幕截图。

并发函数

如果具有连接器或 Dataverse 调用,该 Concurrent 函数允许在同一属性中同时计算多个公式。 通常,当您使用 ;(分号)运算符将多个公式串联时,它们会同时被评估。 使用 Concurrent 时,应用程序会同时评估属性中的所有公式,即使在使用 ; 运算符之后也是如此。 此并发意味着用户等待结果的时间更短。 如果必须等到前一个调用完成才能启动数据调用,则应用必须等待所有请求时间之和。 如果数据调用都同时启动,应用只需等待最长请求的时间。 详细了解 并发函数

Concurrent(
    ClearCollect(colAccounts1, Accounts),
    ClearCollect(colUsers1, Users),
    ClearCollect(colEnvDef1, 'Environment Variable Definitions'),
    ClearCollect(colEnvVal1, 'Environment Variable Values')
);

Coalesce 函数

Coalesce 函数按顺序对参数求值并返回第一个值(不是空白或空字符串)。 使用此函数将空白值或空字符串替换为其他值,但保留非空白和非空字符串值不变。 如果所有参数均为空或空字符串,该函数将返回空值。 Coalesce 是将空字符串转换为空值的好方法。 详细了解 Coalesce 函数

此示例需要 value1value2 评估两次:

If(Not IsBlank(value1), value1, Not IsBlank(value2), value2)

该函数可简化为:

Coalesce(value1, value2)

IsMatch 函数

IsMatch 函数用于检测文本字符串是否与由普通字符、预定义模式或正则表达式组成的模式匹配。 详细了解 IsMatch 函数

例如,此公式与美国的社会保障号码匹配:

IsMatch(TextInput1.Text, "\d{3}-\d{2}-\d{4}")

正则表达式的说明:

  • \\d 匹配任何数位 (0-9)。

  • {3} 指定前面的数字模式 (\d) 应只出现三次。

  • - 匹配连字符。

  • {2} 指定前面的数字模式 (\d) 应只出现两次。

  • {4} 指定前面的数字模式 (\d) 应只出现四次。

IsMatch 的更多示例:

IsMatch(TextInput1.Text, "Hello World")
IsMatch(TextInput1\_2.Text, "(?!^\[0-9\]\\\*$)(?!^\[a-zA-Z\]\\\*$)(\[a-zA-Z0-9\]{8,10})")

优化应用 OnStart

OnStart画布应用的属性在启动应用时发生的动作定义中发挥了关键作用。 此属性允许应用程序开发人员执行全局初始化任务、设置变量以及执行在应用程序启动过程中只发生一次的操作。 了解并有效地使用该 OnStart 属性创建响应且高效的画布应用。

App.OnStart通过将变量设置迁移到命名公式来简化函数。 命名公式(尤其是应用生命周期早期配置的公式)非常有利。 这些公式根据数据调用处理变量的初始化,这为代码提供了更整洁、更组织的结构。 在 生成大型和复杂的画布应用中了解详细信息。

备注

OnStart 属性势在必行。 这是在显示第一个屏幕之前需要完成的工作的有序列表。 因为 不仅对 需要完成的工作内容非常具体,而且 必须在特定的时序下 完成这些工作,这限制了可能的重新排序和延迟优化。

启动屏幕

如果 App.OnStart 包含 Navigate 函数调用,即使它位于函数中 If 并且很少调用,应用也必须在显示应用的第一个屏幕之前完成执行 App.OnStart 。  App.StartScreen 是一种声明性方式,用于指示应首先显示哪个屏幕,并且不会阻止优化。

设置 StartScreen 属性会在 App.OnStart 完成之前显示第一个屏幕。 App.StartScreen 声明先显示哪个屏幕对象,而无需任何预处理。

而不是编写像下面这样的代码:

App.OnStart = Collect(OrdersCache, Orders);
If(Param("AdminMode") = "1", Navigate(AdminScreen), Navigate(HomeScreen))

将代码更改为:

App.OnStart = Collect(OrdersCache, Orders);
App.StartScreen = If(Param("AdminMode") = "1", AdminScreen, HomeScreen)

详细信息: App.StartScreen:App.OnStart 中导航的声明性替代方法

警告

避免 StartScreenOnStart 之间的依赖关系。 引用一个命名公式,而该公式又引用全局变量,可能会导致出现争用条件,从而无法正确应用 StartScreen

不要在StartScreenOnStart之间创建依赖项。 当应用阻止StartScreen中对全局变量的引用时,可以通过引用一个命名的公式来间接引用全局变量。 此方法可能会导致无法正确应用的争用条件 StartScreen

命名公式

命名公式是可以在App.Formulas中定义的静态值或常量。 声明后 App.Formulas,可以在应用中的任意位置使用它们,其值始终保持最新状态。 Power Apps中的命名公式可用于定义平台自动管理和更新的值或值集。 此功能将价值计算和维护的责任从开发人员转移到Power Apps,从而简化开发过程。 Power Apps中的命名公式是一项功能强大的功能,可以显著提高应用性能和可维护性。

声明应用主题时,命名公式也有所帮助。 生成企业应用时,通常希望应用具有提供一致外观和用户体验的常见主题。 若要创建主题,需要在其中声明数十到数百个变量 App.OnStart。 此声明会增加代码长度和应用的初始化时间。

现代控件也能在主题创建上提供极大帮助,并有助于减少用户为处理主题而编写的逻辑。 现代控件当前处于预览状态。

例如,可以将以下代码 App.OnStart 移到 App.Formulas其中,从而减少全局变量声明的启动时间。

Set(BoardDark, RGBA(181,136,99, 1));
Set(BoardSelect, RGBA(34,177,76,1));
Set(BoardRowWidth, 10);                      // expected 8 plus two guard characters for regular expressions.
Set(BoardMetadata, 8 \* BoardRowWidth + 1);   // which player is next, have pieces moved for castling rules, etc.
Set(BoardBlank, "----------------------------------------------------------------\_00000000000000");
Set(BoardClassic, "RNBQKBNR\_\_PPPPPPPP------------------------\_--------\_\_pppppppp\_\_rnbqkbnr\_\_0000000000");

可按如下所示将代码移动到 App.Formulas

BoardSize = 70;
BoardLight = RGBA(240,217,181, 1);
BoardDark = RGBA(181,136,99, 1);
BoardSelect = RGBA(34,177,76,1);
BoardRowWidth = 10;                      // expected 8 plus two guard characters for regular expressions
BoardMetadata = 8 \* BoardRowWidth + 1;   // which player is next, have pieces moved for castling rules, etc.
BoardBlank = "----------------------------------------------------------------\_00000000000000";
BoardClassic = "RNBQKBNR\_\_PPPPPPPP------------------------\_--------\_\_pppppppp\_\_rnbqkbnr\_\_0000000000";

另一个示例是在设置 Lookups中。 此处,Lookup 公式中需要更改才能从 Office 365 而不是 Dataverse 获取用户信息。 只需在一个位置进行更改,而无需在任何地方更改代码。

UserEmail = User().Email;
UserInfo = LookUp(Users, 'Primary Email' = User().Email);
UserTitle = UserInfo.Title;
UserPhone = Switch(UserInfo.'Preferred Phone', 'Preferred Phone (Users)'.'Mobile Phone', UserInfo.'Mobile Phone',
UserInfo.'Main Phone');

这些公式体现了计算的本质。 它们阐明了根据其他值确定 UserEmailUserInfoUserTitleUserPhone 的过程。 此逻辑进行封装后,可在整个应用程序中广泛使用,并可在单一位置进行修改。 适应性扩展到从 Dataverse Users 表切换到 Office 365 连接器,而无需更改分散在应用中的公式。

另一种方法是优化 countRows

varListItems = CountRows(SampleList)

使用Set函数时,必须使用示例列表中的行的初始计数来初始化变量varListItems,并在添加或删除列表项后再次重新设置该变量。 随着数据更改, varListItems 命名公式会自动更新变量。

属性中的 App.Formulas 命名公式提供了一种更灵活且声明性的方法,用于在整个应用中管理值和计算。 与完全 App.OnStart依赖相比,它们在计时独立、自动更新、可维护性和不可变定义方面具有优势。

方面 命名公式 (App.Formulas) App.OnStart
时间独立性 公式立即可用,可以按任意顺序计算。 变量可能会引入影响可用性的计时依赖项。
自动更新 依赖项更改时,公式将自动更新。 在启动期间设置变量一次;可能需要手动更新。
可维护性 将公式集中到一个位置,可提高可维护性。 分散变量可能需要在多个位置查找和更新。
不可变定义 公式 App.Formulas 定义是不可变的。 变量值可能容易受到意外更改的影响。

用户定义函数

在 Power Apps Studio 中,用户定义的函数 允许您创建自己的自定义函数。

如下所示在 App.Formulas 下定义一个公式:

FunctionName(Parameter1:DataType1, Parameter2:DataType2):OutputDataType = Formula

代码的工作原理如下:

  • FunctionName 调用函数。

  • Parameter 是输入的名称。 可以包含一个或多个输入。

  • DataType 是传递给函数的参数必须匹配的数据类型。 可用的数据类型包括布尔值、颜色、日期、日期时间、动态、GUID、超链接、文本和时间。

  • OutputDataType 是函数输出的数据类型。

  • Formula 是函数的输出。

用于 IfError 在定义的函数中实现错误处理:

// Function to calculate the area of a circle based on the radius
calcAreaOfCircle(radius: Number): Number = 
    IfError(Pi() * radius * radius, 0);

从文本或标签控件调用定义的函数。

calcAreaOfCircle(Int(*TextInput1*.Text))

优化变量

变量定义和设置您在整个应用中使用的局部和全局值。 虽然它们很方便,但使用过多的变量会降低应用的效率。

下面的示例演示了为对象的每个属性设置一个变量,这需要为每个属性使用 Set

Set(varEmpName, Office365Users.MyProfile().DisplayName);
Set(varEmpCity, Office365Users.MyProfile().City);
Set(varEmpPhone, Office365Users.MyProfile().BusinessPhones);
Set(varEmpUPN, Office365Users.MyProfile().UserPrincipalName);
Set(varEmpMgrName, Office365Users.ManagerV2(varEmpUPN).DisplayName);

更有效的方法是仅在需要时才使用该属性:

Set(varEmployee, Office365Users.MyProfile())
"Welcome " & varEmployee.DisplayName

合理使用上下文变量和全局变量。 如果变量的范围超出单个屏幕,请使用全局变量而不是上下文变量。

太多未使用的变量会增加内存使用量,并可能减慢应用初始化速度。 即使您不使用这些变量,也会为它们分配资源。 未使用的变量也会增加应用逻辑的复杂性。 保持 Power App 干净有序,以提高性能和简化开发。

优化集合

集合是用于在 Power Apps 应用中存储和处理数据的临时数据存储结构。 但是,集合可能会导致性能开销。 限制集合的使用,仅在必要时使用它们。

// Use this pattern
ClearCollect(colErrors, {Text: gblErrorText, Code: gblErrorCode});

// Do not use this pattern
Clear(colErrors);
Collect(colErrors, {Text: gblErrorText, Code: gblErrorCode});

要计算本地集合中的记录,请使用 CountIf 而不是 Count(Filter())

使用集合时,请考虑使用此方法:

  • 限制集合的大小和数量。 由于集合是应用的本地集合,因此它们存储在移动设备内存中。 集合存储的数据越多,或使用的集合越多,性能越差。 使用 ShowColumns 函数仅获取特定列。 添加 Filter 函数以仅获取相关数据。

    以下示例函数返回整个数据集:

    ClearCollect(colDemoAccount, Accounts);
    

    将此函数与以下代码进行比较,该代码仅返回特定记录和列:

    ClearCollect(colAcc,
      ShowColumns(
        Filter(Accounts, !IsBlank('Address 1: City')),
        "name","address1_city"))
    

    此示例返回以下数据集:

    数据集的屏幕截图,其中包含一个名为 colAcc 的表和两列,列名为 address1_city 和名称。

  • 设置数据源刷新频率。 如果将新记录添加到集合中,请刷新集合或将数据汇集到集合中,以获取新记录或已更改的记录。 如果多个用户更新数据源,请刷新集合以获取新的或已更改的记录。 更多的刷新调用意味着与服务器的交互更多。

缓存集合和变量中的数据

集合是一个表变量,用于存储数据的行和列,而不仅仅是单个数据项。 集合之所以有用,主要有两个原因:在将数据发送到数据源之前聚合数据,以及缓存信息以避免频繁查询。 由于集合与数据源和 Power Apps 的表格结构相匹配,因此即使在脱机状态下,也可以高效地与数据进行交互。

// Clear the contents of EmployeeCollection, it already contains data
ClearCollect(
    colEmployee,
    {
        Id: "1",
        Name: "John",
        Department: "IT"
    },
    {
        Id: "2",
        Name: "Nestor",
        Department: "IT"
    }
)

删除未使用的变量和媒体

虽然未使用的媒体和变量可能不会显著影响应用性能,但通过删除任何未使用的媒体或变量来清理应用非常重要。

  • 未使用的媒体文件会增加应用大小,从而减慢应用加载时间。

  • 未使用的变量会增加内存使用量,并可能略微减慢应用初始化速度。 即使未使用这些变量,也会为这些变量分配资源。 太多未使用的变量也会使应用的逻辑更加复杂。

  • 使用应用检查器查看未使用的媒体和变量。

优化屏幕和控件

若要优化Power Apps中的屏幕和控件,请考虑以下最佳做法。

避免交叉引用控件

引用其他屏幕控件的控件会降低应用程序的加载和导航速度。 此方法可以强制应用加载其他屏幕,而不是等到用户转到该屏幕。 若要解决此问题,请使用变量、集合和导航上下文跨屏幕共享状态。

Power Apps Studio 中的应用检查器显示交叉引用的控件。 定期查看应用检查器以解决此问题。

在下图中,画廊 1 控件在屏幕 2 的标签 2 控件中被引用。

Power Apps Studio 的截图显示交叉引用的控件。

如果在应用的第二个屏幕中引用第一个屏幕中的控件,不会有性能影响,因为第一个屏幕已经加载。 此行为实际上很有用,因为应用是声明性的,而不是使用变量。

如果引用了尚未加载的控件,例如第一个屏幕引用了来自屏幕 3 的一个名为Label 3的控件,应用程序会将该屏幕加载到内存中。

启用文本控件的 DelayOutput 功能

DelayOutput 设置设置为 true 时,在半秒延迟后注册用户输入。 此延迟可用于推迟昂贵的作,直到用户完成输入文本,例如在其他公式中使用输入时进行筛选。

例如,请考虑一个画廊,其项目根据用户在 TextInput 控件中输入的内容进行过滤:

  • 如果将 DelayOutput 设置为 false(默认值),库将在键入任何文本后立即筛选。 如果你的图库中有许多项目,立刻重新加载有改动的图库会降低性能。 最好等待。 使用 TextInput 搜索字符串或 StartsWith 函数时,此行为是可行的。

  • 如果将 DelayOutput 设置为 true,则检测到更改之前存在短暂的延迟。 此延迟提供完成键入的时间。 延迟与该属性 TextInput.OnChange 协同工作良好。 如果您有与更改关联的操作,您会希望在完成输入字段内容后再触发这些操作。

委派和服务器端处理

通过利用委派特性和服务器端处理,应用程序可以通过将操作卸载到数据源,来高效地处理大型数据集。

委托

Power Apps中的委派是指应用程序具有将某些操作卸载到基础数据源的能力,而不是在Power Apps本身中处理这些操作。 通过在 Power Apps 中使用委派,可以创建更高效且可缩放的应用程序,即使在涉及大型数据集的方案中也能很好地运行。 请注意特定数据源和作的委派限制,并相应地设计应用以实现最佳性能。

备注

并非所有函数都可以委派。 了解更多关于委派的信息,请参阅查询限制:委派和查询限制

委派具有多种优势,例如查询优化和支持大型数据集。 此外,如果源数据频繁更改,则委派可帮助使数据保持最新。

减少对数据源的 API 调用

有时,通过在画布应用中执行联接来创建集合似乎很方便。 请看下面的示例。 在此示例中,有两个表:司机和卡车。 该代码创建司机和卡车详细信息的集合,对于每辆卡车,它都会呼叫拥有卡车的司机。

// Bad code
ClearCollect(vartruckdata, AddColumns('Truck Details',
    "CITY",LookUp(Drivers, 'Truck Details'\[@'Dummy ID'\] = Drivers\[@'Truck Details'\],City),
        "FIRSTNAME",LookUp(Drivers, 'Truck Details'\[@'Dummy ID'\] = Drivers\[@'Truck Details'\],'Driver First Name'),
    "LASTNAME",LookUp(Drivers, 'Truck Details'\[@'Dummy ID'\] = Drivers\[@'Truck Details'\],'Driver Last Name'),
        "STATE",LookUp(Drivers, 'Truck Details'\[@'Dummy ID'\] = Drivers\[@'Truck Details'\],State)));

在画布应用中执行此类联接可能会生成对数据源的许多调用,从而导致加载时间变慢。

更好的方法是:

// Good code
Set(
    varTruckData,
    LookUp(
        Drivers,
        'Dummy ID' = ThisRecord.'Dummy ID',
        'Driver First Name'
    ) & LookUp(
        Drivers,
        'Dummy ID' = ThisRecord.'Dummy ID',
        'Driver Last Name'
        )
);

Set(
    varTruckData,
    With(
        {
            vDriver: LookUp(
                Drivers,
                'Dummy ID' = ThisRecord.'Dummy ID'
            )
        },
        vDriver.'Driver First Name' & vDriver.'Driver Last Name'
    )
)

在实时方案中,可以通过修复源中的数据,将加载时间从 5 分钟缩短到 10 秒以下。

服务器端处理

不同的数据源,如 SQL 和 Dataverse,允许您将数据处理任务(如筛选和查找)委托给数据源。 在SQL Server中,可以创建查询定义的视图。 在 Dataverse 中,您可以创建低代码插件在服务器端处理数据,并仅将最终结果返回给您的画布应用。

将数据处理委派给服务器可以提高性能,减少客户端代码,并使应用更易于维护。

了解有关 Dataverse 中插件的更多信息。

优化查询数据模式

优化应用查询数据的方式可以显著减少加载时间并提高整体响应能力。

使用显式列选择

默认情况下,所有新应用都启用显式列选择 (ECS) 功能。 如果您的应用程序未启用,请将其启用。 ECS 会自动将检索到的列数减少到仅在应用中使用的列数。 如果 ECS 未开启,您可能会收到比您需要更多的数据,这可能会影响性能。 有时,当应用通过集合获取数据时,列的原始源可能会丢失。 如果 ECS 无法确定某些列是否被使用,则会删除它们。 若要强制 ECS 保留缺失列,请使用集合引用或控件中的 Power Fx 表达式 ShowColumns

避免调用Power Automate来填充集合

一种常见做法是使用Power Automate提取和填充Power Apps中的集合。 虽然这种方法是有效的,但在有些情况下,可能不是最有效的选择。 调用Power Automate会增加网络延迟和 0.6 秒的性能成本,以实例化Power Automate流。

过度使用Power Automate流也可能导致执行限制和流量限制。 始终评估网络延迟和性能成本之间的权衡。

消除 N+1 问题

N+1 问题是数据库查询中的常见问题,指的是在一个查询中没有获取所有必需的数据,而是进行了多个额外的查询来检索相关数据。 此问题可能会导致性能问题,因为每个额外的查询会产生开销。

用于加载集合的简单调用可以生成对数据源的 N+1 调用:

ClearCollect(MyCollection, OrdersList,
    {
        LookUp(CustomersList,CustomerID = OrdersList[@CustomerID])
    }
)

在画布应用和库的上下文中,使用显示相关记录的数据源和库时,可能会出现 N+1 问题。 当对库中显示的每个项目进行更多的查询时,通常会出现此问题,从而导致性能瓶颈。

使用 SQL Server 中的 View 对象来避免 N+1 查询问题,或更改用户界面以避免触发 N+1 方案。

Dataverse 会自动获取相关表所需的数据,并且可以从相关表中选择列。

ThisItem.Account.'Account Name'

如果 RelatedDataSource 大小较小(少于 500 条记录),请将其缓存在集合中,并使用集合来驱动查找(N+1)查询方案。

限制包大小

尽管Power Apps优化应用加载,但可以采取措施减少应用的占用空间。 对于使用较旧设备的用户或位于延迟较高或带宽较低的地区的用户来说,减少占用空间尤为重要。

  • 评估应用中嵌入的媒体。 如果未使用某些内容,请将其删除。

    例如,嵌入的图像可能会过大。 而不是 PNG 文件,查看是否可以使用 SVG 图像。 请注意在 SVG 图像中使用文本,因为字体必须安装在客户端上。 需要显示文本时,解决方法是将文本标签叠加在图像上。

  • 评估分辨率是否适合外形尺寸。 移动应用的分辨率不必与桌面应用程序的分辨率一样高。 尝试正确平衡图像质量与大小。

  • 如果有未使用的屏幕,请删除它们。 注意不要删除任何只有应用程序制作者或管理员使用的隐藏屏幕。

  • 评估您正尝试将过多的工作流放入一个应用程序中。 例如,您是否在同一应用程序中同时具有管理员屏幕和客户端屏幕? 如果是,请考虑将它们分成单独的应用程序。 此方法还使多个用户能够更轻松地同时处理应用,并且当应用更改需要完整测试通过时,它会限制“爆炸半径”(测试量)。

优化 ForAll

Power Apps中的 ForAll 函数用于循环访问记录表,并将公式或公式集应用于每个记录。 虽然函数本身是多才多艺的,但对函数的 ForAll 不当使用可以快速使应用性能降低。

ForAll 函数是一个单一顺序函数,而不是并发函数。 因此,它一次只查看一条记录,获取结果,然后继续下一条记录,直到它遍历其范围内的所有记录。

避免嵌套 ForAll 这种做法可能会导致指数迭代,并显著影响性能。

ClearCollect(FollowUpMeetingAttendees.ForAll(ForAll(Distinct(AttendeesList.EmailAddress.Address).Lookup(Attendees))))

批处理更新数据库

可以使用 ForAllPatch 批处理更新数据库。 但是,在使用 ForAllPatch 的顺序时要小心。

以下函数是更好的方法,例如:

Patch(SampleFoodSalesData, ForAll(colSampleFoodSales,
    {
        demoName:"fromCanvas2"
    })
);

而以下方法效率较低:

ForAll(colSampleFoodSales, Patch(SampleFoodSalesData,
    {
        demoName:"test"
    })
);

下一步