在模型導向應用程式中設計表單以提高效能

建立可快速且高效率完成工作的體驗,對使用者的滿意度至關重要。 模型導向應用程式可以高度自訂,以建立滿足使用者需求的體驗,但重要的是要知道如何有效率地編碼、組建和執行模型導向應用程式,在使用者開啟和導覽您的應用程式處理日常工作時快速載入。 當應用程式未針對效能進行最佳化時,已證明效能是導致使用者不滿意應用程式的關鍵因素。

智慧自訂和高效能表單是建立高效率且具生產力表單的重要部分。 另外,務必使用使用者介面設計和版面配置中的最佳作法,來組建高效率的表單。 有關設計表單以提高效率和生產力的資訊,請參閱在模型導向應用程式中設計具生產力的主要表單

也必須確保使用者使用建議和支援的裝置,以及最低的必要規格。 更多資訊:支援的網頁瀏覽器和行動裝置

使用資料和資料表

本節說明顯示資料和索引標籤的控制項如何影響表單效能。

預設索引標籤的重要性

預設索引標籤是表單的第一個展開索引標籤。 它在載入表單頁面中扮演特殊角色。 根據設計,預設索引標籤的控制項一律會在開啟紀錄時呈現。 具體來說,會針對索引標籤上的每個控制項,叫用控制項初始化邏輯,例如資料擷取。

相較之下,在初始載入表單時,次要索引標籤不會對其控制項執行此初始化。 而是會在透過使用者互動或呼叫 setFocus 用戶端 API 方法開啟次要索引標籤時,進行控制項初始化。 這可讓您將特定控制項放在次要索引標籤,而非預設索引標籤中,以保護初始表單載入不受過度控制項處理。因此,控制項放置策略可以對初始表單載入的回應性產生顯著影響。 回應速度更快的預設索引標籤提供更好的整體體驗,包含修改重要欄位、與命令列互動、以及探索其他索引標籤和區段。

將最常用的控制項放在預設索引標籤的頂端。使用者與表單上的資料互動時,版面配置和資訊架構不僅對效能很重要,對提高效率也很重要。 其他資訊:在模型導向應用程式中設計高效率的主要表單

資料驅動控制項

需要超出主要記錄之額外資料的控制項,會對表單回應性和載入速度產生極大的壓力。 這些控制項會透過網路擷取資料,並經常涉及等待期間 (視為進度指示器),因為傳輸資料可能需要時間。

部分資料驅動控制項包括:

在預設索引標籤上只保留最常用的控制項。其餘的資料驅動控制項應分發至次要索引標籤中,允許快速載入預設索引標籤。 此外,這種版面配置策略可減少擷取未派上用場之資料的可能性。

雖然其他控制項的影響比資料驅動控制項少,但仍能參與上述版面配置,以達到最佳效能。 這些控制向包括:

網頁瀏覽器

本節說明與網頁瀏覽器搭配使用的良好做法。

請勿開啟新視窗

openForm 用戶端 API 方法允許使用參數選項,在新視窗中顯示表單。 請勿使用此參數或將其設為 False。 將此參數設為 False 可確保該 openForm 方法會執行使用現有視窗顯示表單的預設行為。 也可以從自訂指令碼或其他應用程式中直接呼叫 window.open JavaScript 函數;但應該避免這種情況。 打開新視窗表示需要從頭開始擷取並載入所有頁面資源,因為頁面無法利用先前載入的表單與新視窗中表單之間的記憶體內資料快取功能。 除了打開新視窗之外,請考慮使用多工作階段體驗,讓記錄可以在多個索引標籤中打開,同時仍最大化用戶端快取的效能優勢。

使用新式瀏覽器

使用最新的網頁瀏覽器,是確保模型導向應用程式能儘快執行的關鍵。 這樣做原因是,許多效能改善只能用於較新的新式瀏覽器。

例如,如果您的組織有舊版 Firefox、非 Chromium 式瀏覽器等,則許多內建於模型導向應用程式的效能提升將無法在舊版瀏覽器中使用,因為它們不支援應用程式所依賴的功能,無法快速且順利執行。

在大多數情況下,您只要切換至 Microsoft Edge、從舊版本更新至目前最新的瀏覽器版本,或移至最新的 Chromium 式瀏覽器,就可以發現頁面載入有所改善。

JavaScript 自訂

本節說明如何在使用 JavaScript 時進行智慧自訂,以協助您在模型驅動的應用程式中建立高效能表單與頁面。

將 JavaScript 與表單搭配使用

JavaScript 自訂表單的能力,為專業開發人員在表單外觀和行為方面提供了極大的彈性。 不當使用這種彈性會對表單效能造成負面影響。 開發人員執行 JavaScript 自訂時,應使用以下策略來最大化表單效能。

要求資料時使用非同步網路要求

當需要額外的資料來進行自訂時,請以非同步方式要求資料,而不是同步。 對於支援等待非同步程式碼的事件 (如 OnLoad 表單與 OnSave 表單事件),事件處理常式應傳回 Promise,以便讓平台等到 Promise 已解決為止。 當使用者等待事件完成時,平台將會顯示適當的 UI。

對於不支援等待非同步程式碼的事件 (例如 OnChange 表單事件),您可以使用 showProgressIndicator,在程式碼執行非同步要求時停止與表單互動。 這比使用同步處理要求更好,因為在顯示進度指示器時,使用者仍然可以與應用程式的其他部分互動。

以下是在同步處理擴充點中使用非同步程式碼的範例。

//Only do this if an extension point does not yet support asynchronous code
try {
    await Xrm.WebApi.retrieveRecord("settings_entity", "7333e80e-9b0f-49b5-92c8-9b48d621c37c");
    //do other logic with data here
} catch (error) {
    //do other logic with error here
} finally {
    Xrm.Utility.closeProgressIndicator();
}

// Or using .then/.finally
Xrm.Utility.showProgressIndicator("Checking settings...");
Xrm.WebApi.retrieveRecord("settings_entity", "7333e80e-9b0f-49b5-92c8-9b48d621c37c")
    .then(
        (data) => {
            //do other logic with data here
        },
        (error) => {
            //do other logic with error here
        }
    )
    .finally(Xrm.Utility.closeProgressIndicator);

在不支援等待非同步程式碼的事件處理常式中使用非同步程式碼時必須多加小心。 對於需要對非同步程式碼解析採取行動或處理的程式碼,尤其如此。 如果解析處理常式希望應用程式內容保持與非同步程式碼啟動時的相同,則非同步程式碼可能會發生問題。 您的程式碼應該在每個非同步延續點之後檢查使用者是否處於相同的內容中。

例如,事件處理常式中可能會有發出網路要求的程式碼,並根據回覆資料變更要停用的控制項。 在收到來自要求的回覆之前,使用者可能會與控制項進行互動,或瀏覽至不同的頁面。 因為使用者在不同的頁面上,所以表單內容可能無法使用,這可能會造成錯誤或發生其他意外行為。

OnLoad 表單和 OnSave 表單事件中的非同步支援

OnLoad 表單和 OnSave 事件支援傳回承諾的處理常式。 事件將等待處理常式所傳回的任何承諾解析,直到逾時期限。 可透過應用程式設定來啟用此支援。

其他資訊:

限制表單載入期間要求的資料量

只要求在表單上執行商務規則所需的最少資料量。 盡力快取要求的資料,尤其是對於不常變更或不需要重新整理的資料。 例如,假設有一個表單要求設定資料表中的資料。 根據 [設定] 資料表中的資料,表單可能會選擇隱藏表單的一部分。 在這種情況下,JavaScript 可以在 sessionStorage 中快取資料,因此每個工作階段只需要求一次資料 (onLoad1)。 當 JavaScript 使用 sessionStorage 的資料,同時要求下一次瀏覽到表單 (onLoad2) 的資料時,也可以使用 stale-while-revalidate 策略。 最後,如果連續多次呼叫處理常式 (onLoad3),則可以使用重複資料刪除策略。

const SETTING_ENTITY_NAME = "settings_entity";
const SETTING_FIELD_NAME = "settingField1";
const SETTING_VALUE_SESSION_STORAGE_KEY = `${SETTING_ENTITY_NAME}_${SETTING_FIELD_NAME}`;

// Retrieve setting value once per session
async function onLoad1(executionContext) {
    let settingValue = sessionStorage.getItem(SETTING_VALUE_SESSION_STORAGE_KEY);

    // Ensure there is a stored setting value to use
    if (settingValue === null || settingValue === undefined) {
        settingValue = await requestSettingValue();
    }

    // Do logic with setting value here
}

// Retrieve setting value with stale-while-revalidate strategy
async function onLoad2(executionContext) {
    let settingValue = sessionStorage.getItem(SETTING_VALUE_SESSION_STORAGE_KEY);

    // Revalidate, but only await if session storage value is not present
    const requestPromise = requestSettingValue();

    // Ensure there is a stored setting value to use the first time in a session
    if (settingValue === null || settingValue === undefined) {
        settingValue = await requestPromise;
    }
    
    // Do logic with setting value here
}

// Retrieve setting value with stale-while-revalidate and deduplication strategy
let requestPromise;
async function onLoad3(executionContext) {
    let settingValue = sessionStorage.getItem(SETTING_VALUE_SESSION_STORAGE_KEY);

    // Request setting value again but don't wait on it
    // In case this handler fires twice, don’t make the same request again if it is already in flight
    // Additional logic can be added so that this is done less than once per page
    if (!requestPromise) {
        requestPromise = requestSettingValue().finally(() => {
            requestPromise = undefined;
        });
    }

    // Ensure there is a stored setting value to use the first time in a session
    if (settingValue === null || settingValue === undefined) {
        settingValue = await requestPromise;
    }
    
    // Do logic with setting value here
}

async function requestSettingValue() {
    try {
        const data = await Xrm.WebApi.retrieveRecord(
            SETTING_ENTITY_NAME,
            "7333e80e-9b0f-49b5-92c8-9b48d621c37c",
            `?$select=${SETTING_FIELD_NAME}`);
        try {
            sessionStorage.setItem(SETTING_VALUE_SESSION_STORAGE_KEY, data[SETTING_FIELD_NAME]);
        } catch (error) {
            // Handle sessionStorage error
        } finally {
            return data[SETTING_FIELD_NAME];
        }
    } catch (error) {
        // Handle retrieveRecord error   
    }
}

請使用用戶端 API 中提供的資訊,而不是發出要求。 例如,您可以使用 getGlobalContext.userSettings.roles,而不是在表單載入時要求使用者的資訊安全角色。

僅在需要時載入程式碼

盡量載入特定表單事件所需的程式碼。 如果您的程式碼只適用於 A 表單B 表單,則不應將其包含在為 C 表單 載入的資料庫中。其應位於自己的程式庫中。

如果程式庫僅用於 OnChangeOnSave 事件,請避免在 OnLoad 事件中載入程式庫。 而是將程式庫載入到這些事件中。 如此一來,平台可以推遲載入它們,直到表單載入完成後。 其他資訊:最佳化表單效能

移除產品代碼中的主控台 API 使用方式

請勿使用主控台 API 方法,例如產品代碼中的 console.log。 將資料記錄到主控台可能會大幅增加記憶體需求,而且可能會阻止清理記憶體中的資料。 這可能會造成應用程式隨時間變得較慢,並最終崩潰。

避免記憶體流失

隨著時間推移,程式碼中的記憶體流失可能會造成效能下降,最終導致應用程式崩潰。 當應用程式無法在不再需要時釋放記憶體,就會發生記憶體流失。 透過表單上的所有自訂和程式碼元件,您應該:

  • 仔細考慮和測試任何負責清理記憶體的案例,例如負責管理物件生命週期的類別。
  • 清理所有事件接聽程式和訂閱,尤其是當它位於 window 物件上時。
  • 清理所有計時器,如 setInterval
  • 避免、限制及清理對全域或靜態物件的參考。

對於自訂控制項元件,可以在摧毀方法中進行清理。

如需修正記憶體問題的詳細資訊,請移至此 Edge 開發人員文件

可用於幫助提高應用程式效能的工具

本節說明可協助您瞭解效能問題的工具,並提供如何在模型導向應用程式中最佳化自訂的建議。

效能見解

Performance Insights 是企業應用程式製作者的自助服務工具,可分析執行階段遙測資料,並提供優先建議清單,以協助改善模型導向應用程式的效能。 此功能會提供一組與 Power Apps 模型導向應用程式或客戶接洽應用程式效能相關的分析見解,例如 Dynamics 365 Sales 或 Dynamics 365 Service,包含建議和可操作的項目。 企業應用程式製作者可以在 Power Apps 中查看應用程式層級的詳細效能見解。 其他資訊:什麼是 Performance Insights?(預覽版)

解決方案檢查程式

解決方案檢查工具是一個強大的工具,可分析用戶端和伺服器自訂,以取得效能或可靠性問題。 它可以剖析客戶端 JavaScript、表單 XML 和 .NET 伺服器端外掛程式,並針對可能降低使用者速度的因素提供見解。 我們建議您每次在開發環境中發佈變更時都執行 [解決方案檢查工具],以便在使用者使用前發現所有的效能問題。 其他資訊:在 Power Apps 中使用解決方案檢查工具驗證您的模型導向應用程式

以下是使用解決方案檢查工具發現效能相關問題的範例:

物件檢查程式

物件檢查程式會對解決方案中的元件物件執行即時診斷。 如果偵測到問題,則會傳回說明如何解決問題的建議。 其他資訊:使用物件檢查工具來診斷解決方案元件 (預覽版)

後續步驟

在模型導向應用程式中設計有效率的主要表單