相较于页面路由(Router),组件导航(Navigation)是ArkUI更为推荐我们使用的一种跳转方式。Navigation既能实现页面间的跳转,也能实现组件内部的页面跳转,支持在不同组件间传递跳转参数,提供灵活的跳转栈操作,从而让页面能够更便捷地被访问和复用。由于我们是为了实现LocalStorage在页面间共享状态,所以我们主要学习如何用Navigation的单页面模式替代Router来实现页面路由。
Navigation组件主要包含导航页跟子页,导航页通常是一个页面(@Entry装饰的组件),需要用Navigation容器作为根容器:
Navigation导航方式需要我们自行管理页面栈,因此我们需要在Index中定义一个NavPathStack类型的成员变量pathStack:
Navigation可以通过.mode()属性方法设置显示模式,Navigation的显示模式有三种:单页模式(NavigationMode.Stack)、分栏模式(NavigationMode.Split)和自适应模式(NavigationMode.Auto),默认是Auto(自动根据页面宽度切换Stack或者Split)。由于我们本节是要学习如何用Navigation替代Router,所以将其设置为Stack:
我们给导航页增加一个按钮,为之后的页面跳转做准备。现在我们的导航页就设计好了:
现在我们来编写子页。子页使用NavDestination作为根容器,同样需要有NavPathStack类型的成员变量来操作页面栈:
子页还需要有一个对外暴露的构建函数,用于之后配置在路由表中:
子页需要配置在路由表(routerMap)中。首先我们要到module.json5文件中,指定routerMap使用的配置文件,这里我们指定为profile资源目录下的router_map.json:
来到profile目录,我们新建配置文件router_map.json,在其中对子页进行配置:
在导航页代码中用pathStack的pushPath()或者pushPathByName()方法跳转下一页:
在子页代码中尝试用pathStack的pop方法跳转上一页:
现在我们来看看效果。启动模拟器并运行项目:
我们可以看到,点击导航页的按钮顺利地跳转到了子页,且左上角为我们自动生成了一个返回按钮,也可以顺利地返回导航页。但当我们点击子页中我们自己创建的返回按钮时,却毫无反应。这是因为我们在之前的代码中,导航页跟子页持有的不是同一个页面栈,当我们从导航页跳转到子页时,子页是在导航页的页面栈中被压栈的,所以当我们在子页调用页面栈的pop()方法时,子页的页面栈中没有任何的页面,自然也无法返回上一页了。那如何让子页持有导航页的pathStack呢?ArkUI给我们提供了4种方法,其中@Provide和@Consume的方法官方并不推荐,所以我们在此采用另外一种,通过NavDestination的onReady()方法传递导航页的pathStack:
再次运行项目到模拟器,我们可以看到子页中自定义的返回按钮可以正常触发返回上一页了:
那如何在Navigation的单页面导航中携带参数和获取参数呢?pushPathByName()方法的第二个参数就是页面参数,必须是一个指定类型的对象实例,因此我们需要先定义一个接口或者类(为了之后能在子页中使用,这里我们需要暴露该接口),然后定义一个参数并传到pushPathByName()方法中:
在子页中用PathStack的getParamByIndex()或者getParamByName()方法,配合类型断言获取页面参数(前者是通过子页在页面栈中的索引,后者是通过子页的别名):
运行项目到模拟器,点击跳转下一页,我们可以看到log窗口把我们传递给子页的参数打印出来了:
至此我们就实现了Navigation方式的单页面跳转。我们继续来学习之前的LocalStorage实现页面间状态数据共享。在EntryAbility的onWindowStageCreate()方法中创建LocalStorage的实例对象,然后在WindowStage.loadContent()中作为第二个参数传入:
Index页面中通过LocalStorage.getShared()方法获取传入的localStorage实例对象,作为@Entry的参数加载到页面中:
最后我们在子页中使用@LocalStorageProp或者@LocalStorageLink绑定刚刚在Index页面创建的属性prob:
运行项目到模拟器,我们可以看到prob属性已经能正确被传递到子页了:
小伙伴们在学习的过程中应该也能看明白,Navigation方式的跳转,并不是真正的不同页面之间的跳转,子页其实跟导航页依然在同一颗组件树上,所以Navigation才被称为“组件导航”,导航页跟子页间才能使用@Provide和@Consume去传递pathStack(官方并不推荐),LocalStorage才能在这样的“不同页面”之间实现状态共享。我们接下来学习的AppStorage。才是真正的能做到在不同页面甚至是整个应用的不同Ability之间共享状态数据。
AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。AppStorage是单例,因此不需要像LocalStorage那样自行创建实例对象,我们直接调用其接口设置或创建需要存储的状态数据即可:
之后的使用跟LocalStorage类似,既可以通过程序逻辑去操作AppStorage中的数据,也可以通过@StorageProp和@StorageLink实现数据的单向或双向绑定。我们新建一个可以被router跳转的页面RouterPage,并在Index页面中实现点击跳转:
运行项目到真机,我们可以看到RouterPage页面是能够正常地共享在Index页面中AppStorage创建的数据的:
LocalStorage和AppStorage都是运行时的内存,但是在应用退出再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStorage。
PersistentStorage将选定的AppStorage属性保留在设备磁盘上。应用程序通过API,以决定哪些AppStorage属性应借助PersistentStorage持久化。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。
PersistentStorage和UI实例相关联,持久化操作需要在UI实例初始化成功后(即windowStage.loadContent()传入的回调被调用时)才可以被调用,早于该时机调用会导致持久化失败:
我们在Index页面新增一个TextInput组件,把内容实时更新到asProb中,看看是否能够顺利持久化(注意这里必须把调用AppStorage.setOrCreate()方法的代码去掉,否则由于每次重新进入应用都会加载页面也就相当于每次都调用该代码,导致持久化的数据被覆盖):
运行项目到真机,我们尝试在TextInput中修改被持久化的数据,然后杀掉应用进程再重新进入,可以看到TextInput中依然是我们上次修改后的内容,说明我们的数据持久化成功了: