viewmodel 사용

완료됨

MVVM 패턴을 구성하는 구성 요소에 대해 학습한 후에는 모델과 뷰를 쉽게 정의할 수 있다는 것을 알게 될 것입니다. viewmodel을 사용하여 패턴에서 해당 역할을 더 잘 정의하는 방법을 살펴보겠습니다.

사용자 인터페이스에 속성 공개

이전 예제와 같이, viewmodel은 대부분의 데이터와 비즈니스 논리를 모델에 의존합니다. 하지만 현재 뷰에 필요한 어떠한 방식으로든 데이터의 형식을 지정하고, 변환하고, 보강합니다.

viewmodel을 사용하여 형식 지정

휴가 시간 형식을 지정하는 예제는 이미 살펴보았습니다. 날짜 형식 지정, 문자 인코딩 및 직렬화는 모두 viewmodel이 모델의 데이터에 형식을 지정하는 방식의 예입니다.

viewmodel을 사용하여 변환

모델은 간접적인 방법으로 정보를 제공합니다. 하지만 viewmodel이 이것을 수정하는 경우가 많습니다. 예를 들어, 직원이 감독자인지 여부를 화면에 표시해야 한다고 가정하겠습니다. 하지만 Employee 모델은 해당 정보를 직접 제공하지 않습니다. 대신 해당 직원에게 보고하는 다른 사람이 있는지 여부에 따라 이 사실을 유추해야 합니다. 모델에 이 속성이 있다고 가정합니다.

public IList<Employee> DirectReports
{
    get
    {
        ...
    }
}

목록이 비어 있으면 이 Employee는 감독자가 아니라고 유추할 수 있습니다. 이 경우 EmployeeViewModel에는 해당 논리를 제공하는 IsSupervisor 속성이 포함됩니다.

public bool IsSupervisor => _model.DirectReports.Any();

viewmodel을 사용하여 보강

모델이 관련 데이터의 ID만 제공하는 경우도 있습니다. 또는 단일 화면에 필요한 데이터의 상관 관계를 지정하는 여러 모델 클래스로 이동해야 할 수 있습니다. viewmodel은 이러한 작업을 수행하기에 적합합니다. 한 직원이 현재 관리 중인 프로젝트를 모두 표시하려는 경우를 가정해 보겠습니다. 이 데이터는 Employee 모델 클래스의 일부가 아닙니다. CompanyProjects 모델 클래스를 확인하면 액세스할 수 있습니다. EmployeeViewModel은, 늘 그렇듯이, 이 작업을 공용 속성으로 공개합니다.

public IEnumerable<string> ActiveProjects => CompanyProjects.All
    .Where(p => p.Owner == _model.Id && p.IsActive)
    .Select(p => p.Name);

viewmodel을 사용한 통과 속성 사용

모델이 제공하는 정확한 속성이 viewmodel에 필요한 경우가 자주 있습니다. 해당 속성의 경우 viewmodel은 데이터를 통과시킵니다.

public string Name
{
    get => _model.Name;
    set => _model.Name = value;
}

viewmodel의 범위 설정

viewmodel은 뷰가 있는 모든 수준에서 사용할 수 있습니다. 페이지에는 대개 viewmodel이 있지만 페이지의 하위 뷰에도 viewmodel이 있을 수 있습니다. viewmodel이 중첩되는 한 가지 일반적인 이유는 페이지에 ListView가 표시되는 경우입니다. 목록에는 컬렉션을 나타내는 viewmodel(예: EmployeeListViewModel)이 있습니다. 목록의 각 요소는 EmployeeViewModel입니다.

여러 EmployeeViewModel 하위 개체가 있는 EmployeeListViewModel의 다이어그램

전체 애플리케이션의 데이터와 상태를 보유하지만 특정 페이지와 연결되지 않은 최상위 viewmodel이 있는 경우도 일반적입니다. 이러한 viewmodel은 주로 "활성" 항목을 유지 관리하는 경우에 사용됩니다. 방금 설명한 ListView 예제를 고려해 보세요. 사용자가 직원 행을 선택하면 해당 직원은 ‘현재 항목’을 나타냅니다. 사용자가 세부 정보 페이지로 이동하거나, 행이 선택된 상태에서 도구 모음 단추를 선택하면, 직원에 해당하는 작업이나 디스플레이가 표시되어야 합니다. 이러한 시나리오를 처리하는 멋진 방법은 도구 모음이나 세부 정보 페이지가 액세스할 수 있는 속성에 ListView.SelectItem 데이터 바인딩을 사용하는 것입니다. 해당 속성을 중앙 viewmodel에 배치하면 잘 작동합니다.

뷰와 viewmodel을 다시 사용하는 경우를 식별

viewmodel과 모델 사이의 관계와 viewmodel과 뷰 사이의 관계를 정의하는 방법은 규칙보다는 앱 요구 사항으로 더 많이 지정됩니다. viewmodel의 목적은 필요한 구조와 데이터를 뷰에 제공하는 것입니다. Viewmodel 범위의 "규모"를 결정할 때 이를 기준으로 삼아야 합니다.

viewmodel은 모델 클래스의 구조를 면밀하게 반영하고 해당 클래스와 일대일 관계를 유지하게 되는 경우가 많습니다. 앞에서 EmployeeViewModel을 사용하여 단일 Employee 인스턴스를 래핑하고 보강한 예제를 보았습니다. 하지만 항상 일대일 관계는 아닙니다. viewmodel이 뷰에 필요한 것을 제공하도록 디자인된 경우 어떤 모델과도 명시적 관계가 없지만 모든 모델 클래스의 데이터를 사용할 수 있는 HR 부서에 대한 개요를 제공하는 HRDashboardViewModel과 같은 디자인이 될 수 있습니다.

마찬가지로 viewmodel과 는 일대일 관계인 경우가 많습니다. 그러나 이 역시 항상 그런 것은 아닙니다. 각 직원에 대한 행을 보여주는 ListView를 다시 생각해보겠습니다. 행 중 하나를 선택하면 직원 세부 정보 페이지로 이동됩니다.

목록 페이지에는 컬렉션이 포함된 viewmodel이 있습니다. 앞서 제안했듯이 이 컬렉션은 EmployeeViewModel 개체의 컬렉션일 수 있습니다. 그리고 사용자가 행을 선택하면 EmployeeViewModel 인스턴스가 EmployeeDetailPage에 전달될 ‘수 있습니다’. 그리고 세부 정보 페이지는 EmployeeViewModelBindingContext로 사용할 ‘수 있습니다’.

이 시나리오는 viewmodel을 재사용하기 아주 좋은 기회일 수 있습니다. 단, viewmodel은 뷰에 필요한 사항을 제공하기 위한 수단이라는 것을 기억하세요. 경우에 따라, 모두 동일한 모델 클래스를 기반으로 하더라도 별도의 viewmodel이 필요할 수도 있습니다. 이 예제에서 ListView 행에는 전체 세부 정보 페이지보다 훨씬 적은 정보가 필요할 가능성이 많습니다. 세부 정보 페이지에 필요한 데이터를 검색하는 데 오버헤드가 많이 발생하는 경우 이러한 각각의 뷰를 처리하는 EmployeeListRowViewModelEmployeeDetailViewModel 모델을 모두 사용할 수 있습니다.

Viewmodel 개체 모델

INotifyPropertyChanged를 구현하는 기본 클래스를 사용하면 모든 viewmodel에서 인터페이스를 다시 구현할 필요가 없습니다. 이 학습 모듈의 이전 부분에 설명된 대로 HR 애플리케이션을 고려합니다. EmployeeViewModel 클래스는 INotifyPropertyChanged 인터페이스를 구현하고 PropertyChanged 이벤트를 발생시킬 OnPropertyChanged라는 도우미 메서드를 제공했습니다. 직원에게 할당된 리소스를 설명하는 것과 같이, 프로젝트의 다른 viewmodel도 뷰와 완전히 통합하려면 INotifyPropertyChanged가 필요합니다.

.NET 커뮤니티 도구 키트의 일부인 MVVM 도구 키트 라이브러리는 MVVM 패턴을 사용하여 최신 앱을 빌드하기 위한 시작 구현을 제공하는 표준, 자체 포함, 경량 형식의 컬렉션입니다.

고유한 viewmodel 기본 클래스를 작성하는 대신 viewmodel 기본 클래스에 필요한 모든 것을 제공하는 도구 키트의 ObservableObject 클래스에서 상속합니다. 다음에서 EmployeeViewModel을 간소화할 수 있습니다.

using System.ComponentModel;

public class EmployeeViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private Employee _model;

    public string Name
    {
        get {...}
        set
        {
            _model.Name = value;
            OnPropertyChanged(nameof(Name))
        }
    }

    protected void OnPropertyChanged(string propertyName) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

다음 코드로 이동:

using Microsoft.Toolkit.Mvvm.ComponentModel;

public class EmployeeViewModel : ObservableObject
{
    private string _name;

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

MVVM 도구 키트는 CommunityToolkit.Mvvm NuGet 패키지를 통해 배포됩니다.

지식 확인

1.

.NET MAUI와 함께 MVVM 패턴을 사용하는 경우 모델, 뷰, viewmodel이 서로 완전히 분리되지는 않습니다. MVVM 조각 간의 일반적인 종속성을 설명하는 항목은 무엇인가요?

2.

대체로 플랫폼에 단단히 결합되어 단위 테스트를 만들기가 어려운 항목은 모델, 뷰 또는 viewmodel 중 무엇인가요?