Post

Maui最佳实践之 导航

客户端开发和前端开发不同,前端有丰富且多样的框架、组件库、文档,且大多数开源项目都非常优秀,使得开发一个web单页应用变得非常容易。但在前端中耳熟能详的“路由”、“状态管理”、“组件化”等概念,在客户端领域资料甚少。其次js与其他强类型语言不同,许多设计模式的实现,两者完全不同。

Maui最佳实践系列将以微软官方案例应用——eShop为例,分析其在架构设计、常用功能的封装和实现上的可取之处。

导航功能说明

应用中的导航,就是“视图”的跳转。在手机应用中,底部Tab栏的页面跳转,顶部导航栏提供路由返回功能,以及App左侧浮动菜单栏。这都是常见的“导航功能”在UI上的体现。

导航功能需要从 PageA -> PageB,并且可以传递参数,而且要提供“返回上一级”的功能。

根据功能说明,很容易想到,导航应该考虑下面几个问题:

  1. 避免页面间依赖:当从A页面到B时,不应该从A页面的逻辑中创建B。被导航的页面之间,都应该彼此无依赖关系,如同UI中的Tab导航栏,用户点击Tab图标跳转到某个页面,与当前页面无关。即便用户需要点击当前页面的按钮,触发导航,那么也应该将目标页面的创建交给“导航器”。
  2. 页面的依赖注入:当把页面的创建交给“导航器”,那么导航器就要根据页面需要进行依赖项处理。在Mvvm架构中,一个页面对应若干ViewModel,而ViewModel可能需要访问依赖容器中的其他服务。如果使用构造函数注入,那么导航器自然要在创建页面时处理依赖注入问题。
  3. 在任何地方访问导航器
  4. 合理传递导航参数,并初始化目标视图。
  5. 导航行为需要为页面提供干预手段,

解决方案

关键点1 创建导航服务,并作为单例注册到应用的DI容器

关键点2 使用Shell的导航功能实现页面的注册和切换

关键点3 视图模型优先导航

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface INavigationService{
  Task InitializeAsync();

  Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null);

  Task PopAsync();
}

public class MauiNavigationService : INavigationService
{
    private readonly IAppEnvironmentService _appEnvironmentService;

    public MauiNavigationService(IAppEnvironmentService appEnvironmentService)
    {
        _appEnvironmentService = appEnvironmentService;
    }

    public async Task InitializeAsync()
    {
        var user = await _appEnvironmentService.IdentityService.GetUserInfoAsync();

        await NavigateToAsync(user == UserInfo.Default ? "//Login" : "//Main/Catalog");
    }

    public Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null)
    {
        var shellNavigation = new ShellNavigationState(route);

        return routeParameters != null
            ? Shell.Current.GoToAsync(shellNavigation, routeParameters)
            : Shell.Current.GoToAsync(shellNavigation);
    }

    public Task PopAsync()
    {
        return Shell.Current.GoToAsync("..");
    }
}

注入DI

1
2
3
4
// MauiProgram.cs
builder.services.AddSingleton<INavigationService, MauiNavigationService>();

// ViewModel 和 View 也使用DI管理

ViewModel构造函数中使用Navigation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public partial LoginViewModel:ViewModelBase
{
  // ...
  public LoginViewModel(INavigationService navigationService):base(navigationService){
    //...
  }
}

// ViewModelBase
public partial ViewModelBase:ObservableObject{
  public INavigationService NavigationService {get;}
  public ViewModelBase(INavigationSErvice navigationService){
    NavigationService = navigationService;
  }
}

可以写一个ViewModelBase的基类,包含导航器,以及依赖注入。这样只需在子视图模型中使用基类构造函数即可完成导航器的注入。

相关文档

MAUI官方文档-Shell

eShop-Maui

This post is licensed under CC BY 4.0 by the author.