모델 기반 앱의 성능을 위한 디자인 양식

작업을 빠르고 효율적으로 완료할 수 있는 경험을 구축하는 것은 사용자 만족도에 매우 중요합니다. 모델 기반 앱은 사용자 요구에 맞는 환경을 만들기 위해 고도로 사용자 지정할 수 있지만, 일상적인 작업을 수행하는 동안 사용자가 앱에서 열고 탐색할 때 빠르게 로드되는 모델 기반 앱을 효과적으로 코드화, 빌드 및 실행하는 방법을 아는 것이 중요합니다. 성능은 앱이 성능에 최적화되어 있지 않을 때 앱 불만족의 주요 동인인 것으로 나타났습니다.

지능적인 사용자 지정 및 성능 양식은 매우 효율적이고 생산적인 양식을 구축하는 데 중요한 측면입니다. 사용자 인터페이스 디자인 및 레이아웃의 모범 사례를 사용하여 매우 생산적인 양식을 작성하고 있는지 확인하는 것도 중요합니다. 효율성과 생산성을 위한 양식 디자인에 대한 자세한 내용은 모델 기반 앱에서 생산적인 기본 양식 디자인을 참조하세요.

또한 사용자가 권장 및 지원되는 장치와 최소 요구 사양을 사용하고 있는지 확인하는 것이 중요합니다. 추가 정보: 지원되는 웹 브라우저 및 모바일 장치

데이터 및 탭 작업

이 섹션에서는 데이터 및 탭을 표시하는 컨트롤이 양식 성능에 미치는 영향을 설명합니다.

기본 탭의 중요성

기본 탭은 양식의 첫 번째 확장 탭입니다. 양식 페이지를 로드하는 데 특별한 역할을 합니다. 기본적으로 기본 탭의 컨트롤은 레코드를 열 때 항상 렌더링 됩니다. 특히 데이터 검색과 같은 컨트롤 초기화 논리는 탭의 모든 컨트롤에 대해 호출됩니다.

대조적으로, 보조 탭은 양식이 처음 로드될 때 해당 컨트롤에서 이 초기화를 수행하지 않습니다. 대신 컨트롤 초기화는 사용자 상호 작용을 통해 또는 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 이벤트의 비동기 지원

OnLoadOnSave 이벤트 형식은 약속을 반환하는 핸들러를 지원합니다. 이벤트는 처리기에서 반환된 약속이 해결될 때까지 시간 초과 기간까지 기다립니다. 이 지원은 앱 설정을 통해 활성화할 수 있습니다.

추가 정보:

양식 로드 중 요청된 데이터 양 제한

양식에서 비즈니스 로직을 수행하는 데 필요한 최소한의 데이터만 요청하십시오. 특히 자주 변경되지 않거나 최신 정보가 필요하지 않은 데이터의 경우 요청된 데이터를 최대한 캐시합니다. 예를 들어 설정 테이블에서 데이터를 요청하는 양식이 있다고 가정해 보십시오. 설정 테이블의 데이터를 기반으로 양식은 양식의 섹션을 숨기도록 선택할 수 있습니다. 이 경우 JavaScript은 데이터가 세션당 한 번만 요청되도록 sessionStorage에 데이터를 캐시할 수 있습니다(onLoad1). 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용으로 로드되는 라이브러리에 포함되지 않아야 합니다. 자체 라이브러리에 있어야 합니다.

OnChange 또는 OnSave 이벤트에만 사용되는 경우 OnLoad 이벤트에서 라이브러리를 로드하지 마십시오. 대신 해당 이벤트에서 로드하십시오. 이런 식으로 플랫폼은 양식이 로드될 때까지 로드를 연기할 수 있습니다. 추가 정보: 양식 성능 최적화

프로덕션 코드에서 콘솔 API 사용 제거

프로덕션 코드에서 console.log 같은 콘솔 API 메소드를 사용하지 마세요. 콘솔에 데이터를 로깅하면 메모리 요구가 크게 증가하고 데이터가 메모리에서 정리되지 않을 수 있습니다. 이로 인해 시간이 지남에 따라 앱이 느려지고 결국 충돌이 발생할 수 있습니다.

메모리 누출 방지

코드의 메모리 누출로 인해 시간이 지남에 따라 성능이 느려지고 결국 앱이 충돌할 수 있습니다. 메모리 누출은 더 이상 필요하지 않을 때 응용 프로그램이 메모리를 해제하지 못할 때 발생합니다. 양식의 모든 사용자 지정 및 코드 구성 요소를 사용하여 다음을 수행해야 합니다.

  • 개체의 수명 주기 관리를 담당하는 클래스와 같이 메모리 정리를 담당하는 모든 항목에 대한 시나리오를 철저히 고려하고 테스트합니다.
  • 특히 window 개체에 있는 경우 모든 이벤트 리스너 및 구독을 정리합니다.
  • setInterval과 같은 모든 타이머를 정리합니다.
  • 전역 또는 정적 개체에 대한 참조를 피하고 제한하고 정리합니다.

사용자 지정 컨트롤 구성 요소의 경우 파괴 메서드에서 정리를 수행할 수 있습니다.

메모리 문제 수정에 대한 자세한 내용은 이 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. OnLoadEnableRule 양식에서 refreshRibbon을 사용하지 마십시오.

개체 검사기

개체 검사기는 솔루션 내의 구성 요소 개체에 대한 실시간 진단을 실행합니다. 문제가 감지되면 문제 해결 방법을 설명하는 권장 사항이 반환됩니다. 추가 정보: 객체 검사기를 사용하여 솔루션 구성 요소 진단(프리뷰)

다음 단계

모델 기반 앱의 생산적인 기본 양식 디자인