モデル駆動型アプリのパフォーマンスのためのフォームの設計

タスクを迅速かつ効率的に完了できるエクスペリエンスを構築することは、ユーザーの満足度にとって非常に重要です。 モデル駆動型アプリは、ユーザーのニーズを満たすエクスペリエンスを作成するために高度にカスタマイズできますが、ユーザーが日常業務中にアプリを開いて移動したときに、迅速に読み込まれるモデル駆動型アプリを効果的にコード化、ビルド、実行する方法を知っておくことが重要です。 パフォーマンスが最適化されていない場合、パフォーマンスがアプリの不満の主な要因であることが示されています。

インテリジェントなカスタマイズとパフォーマンスに優れたフォームは、非常に効率的で生産性の高いフォームを構築するための重要な要素です。 また、ユーザー インターフェイスのデザインとレイアウトのベスト プラクティスを使用して、生産性の高いフォームを作成することも重要です。 効率と生産性のためのフォームの設計については、モデル駆動型アプリで生産的なメイン フォームを設計する を参照してください。

また、ユーザーが推奨およびサポートされているデバイスと最低限必要な仕様を使用していることを確認することも重要です。 詳細: サポートされる Web ブラウザーとモバイル デバイス

データとタブの操作

このセクションでは、データとタブを表示するコントロールがフォームのパフォーマンスに与える影響について説明します。

既定のタブの重要性

既定のタブは、フォーム上で最初に展開されるタブです。 フォーム ページの読み込みで特別な役割を果たします。 設計上、既定のタブのコントロールは、レコードを開くときに常に表示されます。 具体的には、データ取得などのコントロール初期化ロジックが、タブ上のすべてのコントロールに対して呼び出されます。

一方、セカンダリ タブでは、フォームが最初に読み込まれたときに、コントロールに対してこの初期化を実行しません。 代わりに、コントロールの初期化は、ユーザーの操作または setFocus クライアント API メソッドの呼び出しによってセカンダリ タブが開かれたときに行われます。 これにより、特定のコントロールを既定のタブではなくセカンダリ タブに配置することで、過度なコントロール処理から初期のフォームの読み込みを保護することができます。したがって、コントロール配置方法は、初期のフォームの読み込みの応答性に大きな影響を与える可能性があります。 より応答性の高い既定のタブでは、重要なフィールドの変更、コマンドバーの操作、他のタブやセクションの探索の全体的なエクスペリエンスが向上します。

最もよく使用されるコントロールは、常に既定のタブの上部に配置します。レイアウトと情報アーキテクチャは、パフォーマンスだけでなく、ユーザーがフォーム上のデータを操作する際の生産性を向上させるためにも重要です。 詳細: モデル駆動型アプリで生産的なメイン フォームを設計する

データ駆動型コントロール

プライマリ レコード以外の追加データを必要とするコントロールは、フォームの応答性と読み込み速度に最も大きな負荷をかけます。 これらのコントロールでは、ネットワーク経由でデータを取得し、データの送信に時間がかかる可能性があるため、多くの場合、待機期間 (進行状況インジケーターとして表示) を伴います。

データ駆動型コントロールには、次のものがあります:

これらのコントロールのうち、最も頻繁に使用するものだけを既定のタブに配置します。残りのデータ駆動型コントロールは、既定のタブを迅速に読み込むために、セカンダリ タブに分散する必要があります。 さらに、このレイアウト方法により、使用されずに終わるデータを取得する可能性が低くなります。

データ駆動型コントロールよりも影響は少ないものの、最高のパフォーマンスを達成するために上記のレイアウト方法に関与できる他のコントロールもあります。 これらのコントロールは次のとおりです:

Web ブラウザー

このセクションでは、Web ブラウザで使用するためのグッド プラクティスについて説明します。

新しいウィンドウを開かない

openForm クライアント API メソッドを使用すると、パラメータ オプションでフォームを新しいウィンドウに表示できます。 このパラメーターを使用したり、false に設定したりしないでください。 false に設定すると、openForm メソッドは、既存のウィンドウを使用してフォームを表示する既定の動作を実行します。 カスタム スクリプトまたは別のアプリケーションから window.open JavaScript 関数を直接呼び出すことも可能です; しかし、これも避ける必要があります。 新しいウィンドウを開くということは、ページが前に読み込まれたフォームと新しいウィンドウのフォームの間でメモリ内データ キャッシュ機能を活用できないため、すべてのページ リソースを最初から取得して読み込む必要があることを意味します。 新しいウィンドウを開く代わりに、クライアント キャッシュのパフォーマンス上のメリットを最大限に利用しながら、レコードを複数のタブで開くことができるマルチセッション エクスペリエンスの使用を検討してください。

最新のブラウザーを使用する

最新の Web ブラウザーを使用することは、モデル駆動型アプリを可能な限り高速に実行するための鍵となります。 この理由は、パフォーマンス向上の多くは、新しい最新のブラウザーでしか使用できないからです。

たとえば、組織に古いバージョンの 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 のイベントでの非同期サポート

フォーム OnLoadOnSave イベントは、promise を返すハンドラーをサポートします。 イベントは、ハンドラーから返された promises が解決されるまで (タイムアウト期間まで) 待機します。 このサポートは、アプリの設定で有効にできます。

詳細情報:

フォームの読み込み中に要求されるデータ量を制限する

フォームでビジネス ロジックを実行するために必要な最小限のデータのみを要求します。 特に頻繁に変更されないデータや更新する必要のないデータについては、要求されたデータを可能な限りキャッシュします。 たとえば、設定 テーブルからデータを要求するフォームがあるとします。 設定テーブルのデータに基づいて、フォームはフォームのセクションを非表示にすることができます。 この場合、JavaScript はデータを sessionStorage にキャッシュして、データがセッション (onLoad1) ごとに 1 回だけ要求されるようにできます。 また、stale-while-revalidate の方法は、JavaScript がフォーム (onLoad2) への次のナビゲーションのためにデータを要求しながら、sessionStorage からのデータを使用する場合にも使用されることがあります。 最後に、重複排除方法は、ハンドラーが連続して複数回呼び出される場合 (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 の使用を削除する

本番コードでは、console.log などのコンソール API メソッドを使用しないでください。 コンソールにデータを記録すると、メモリの需要が大幅に増加し、データがメモリ内でクリーンアップされなくなる可能性があります。 これにより、時間の経過とともにアプリの動作が遅くなり、最終的にはクラッシュする可能性があります。

メモリ リークを回避する

コードのメモリ リークにより、時間の経過とともにパフォーマンスが低下し、最終的にアプリがクラッシュする可能性があります。 メモリ リークは、アプリケーションが不要になったときにメモリを解放できなかった場合に発生します。 フォーム上のすべてのカスタマイズとコード コンポーネントを使用して、次のことを行う必要があります。

  • オブジェクトのライフサイクルの管理を担当するクラスなど、メモリのクリーンアップを担当するものについては、シナリオを十分に検討してテストしてください。
  • すべてのイベント リスナーとサブスクリプション、特に、window オブジェクト上のものををクリーンアップします。
  • setInterval のようなすべてのタイマーをクリーンアップします。
  • グローバル オブジェクトまたは静的オブジェクトへの参照を回避、制限、およびクリーンアップします。

カスタム制御コンポーネントの場合、クリーンアップは destroy 方法で実行できます。

メモリの問題を修正する方法の詳細については、この Edge 開発者向けドキュメント にアクセスしてください。

アプリのパフォーマンスを向上させるために使用できるツール

このセクションでは、パフォーマンスの問題を理解するのに役立つツールについて説明し、モデル駆動型アプリのカスタマイズを最適化する方法に関する推奨事項を示します。

パフォーマンス分析情報

パフォーマンス分析情報は、ランタイム テレメトリ データを分析し、モデル駆動型アプリのパフォーマンスの向上に役立つ推奨事項の優先リストを提供する、エンタープライズ アプリ作成者向けのセルフ サービス ツールです。 この機能では、Dynamics 365 Sales や Dynamics 365 Service などの Power Apps のモデル駆動型アプリや顧客エンゲージメントアプリのパフォーマンスに関連する分析情報を、推奨事項や実行可能な項目とともに毎日提供しています。 エンタープライズ アプリ作成者は、Power Apps のアプリ レベルで詳細なパフォーマンス分析情報を確認できます。 詳細: パフォーマンス分析情報とは何ですか? (プレビュー)

ソリューション チェッカー

ソリューション チェッカーは、パフォーマンスや信頼性の問題についてクライアントとサーバーのカスタマイズを分析できる強力なツールです。 クライアント側の JavaScript、フォーム XML、.NET サーバー側のプラグインを解析し、エンド ユーザーの速度を低下させる可能性のあるものについて的を絞った分析情報を提供できます。 開発環境で変更を公開するたびにソリューション チェッカーを実行し、パフォーマンス上の問題がエンド ユーザーに届く前に明らかになるようにすることをお勧めします。 詳細: ソリューション チェッカーをを使用して、Power Apps のモデル駆動型アプリを検証する

ソリューション チェッカーで検出されたパフォーマンス関連の問題の例:

  • il-specify-column。 Dataverse クエリ API を通じてすべての列を選択しないでください。
  • web-use-async。 HTTP および HTTPS リソースを非同期に操作します。
  • web-avoid-ui-refreshribbon。 フォーム OnLoad および EnableRulerefreshRibbon を使用しないでください。

オブジェクト チェッカー

オブジェクト チェッカーは、ソリューション内のコンポーネント オブジェクトに対してリアルタイムの診断を実行します。 問題が検出されると、問題の修正方法を説明する推奨事項が返されます。 詳細: オブジェクト チェッカーを使用してソリューション コンポーネントを診断する (プレビュー)

次の手順

モデル駆動型アプリで生産的なメイン フォームを設計する