我经常被要求帮忙解决各种 NativeScript 挑战。我想分享一下最近遇到的一个问题,这个问题在过去也出现过几次。问题如下:
挑战
我有一个类似报纸的 Angular 主细节应用程序。主页面显示要阅读的文章列表。当你点击其中一篇文章时,应用程序就会导航(并使用一个漂亮的页面过渡效果),并将 articleID 传递到细节页面。
这里有一个关键点。在我的细节页面,我的应用程序会显示其他相关文章。当用户点击另一篇文章时,应用程序会重新导航到细节页面,并使用新的 articleId。此时我可以检索新的 articleId 并加载新文章。但是,问题在于,由于应用程序导航到同一个组件,我无法使用页面过渡,而且在 iOS 上,**操作栏**中的**返回按钮**仍然指向主页面。
在有人开始新的 Twitter 战争之前,我要说这是**预期的行为**。让我解释一下。
创建一个新组件需要付出代价,因为它需要大量的处理周期来创建页面以及每个 Native UI 组件。因此,理想情况下,我们应该尽可能避免这种情况。
在移动应用程序中,当你导航回前一页(例如,从细节页面导航回主页面)时,你想要避免创建新组件。在这种情况下,你希望你的应用程序快速导航,没有任何延迟。
为了实现这一点,我们需要实现一个**自定义路由复用策略**(CRRS)。问题是,Angular 提供的默认 CRRS 会在重新导航到自身时自动复用组件……因此产生了挑战。
你可以在Angular 文档中了解更多信息。
我的第一直觉是做一个快速的补丁。如果从 DetailComponent 导航到 DetailComponent 会导致问题,那么为什么不创建该组件的副本,并将导航从**DetailA**到**DetailB**,再到**DetailA**,以此类推呢?
类似于这样:
export class ItemDetailComponent implements OnInit {
protected container = 'a';
private item: Item;
get detailsPath(): string {
return (this.container === 'a') ? '/item-b' : '/item-a';
}
constructor(protected itemService: ItemService, protected route: ActivatedRoute, protected router: RouterExtensions) { }
ngOnInit(): void {
const id = +this.route.snapshot.params['id'];
this.item = this.itemService.getItem(id);
}
navigate(id: number) {
this.router.navigate([this.detailsPath, id]);
}
}
@Component({
selector: 'ns-details-a',
moduleId: module.id,
templateUrl: './item-detail.component.html',
})
export class ItemDetailComponentA extends ItemDetailComponent {
constructor(itemService: ItemService, route: ActivatedRoute, router: RouterExtensions) {
super(itemService, route, router);
this.container = 'a';
}
}
@Component({
selector: 'ns-details-b',
moduleId: module.id,
templateUrl: './item-detail.component.html',
})
export class ItemDetailComponentB extends ItemDetailComponent {
constructor(itemService: ItemService, route: ActivatedRoute, router: RouterExtensions) {
super(itemService, route, router);
this.container = 'b';
}
}
在Playground中查看完整示例。
虽然这种方法有效,但它不是一个很好的解决方案,它是一个很好的补丁,但不是一个很好的解决方案。最重要的是:
如果你有多个这样的细节组件怎么办?我们会开始质疑 Angular 和 NativeScript 是否是适合我们工作需要的框架吗?
然后我和 NativeScript 核心团队的 Alex Vakrilov 谈了谈……他说:
好吧,回到最初的设计图纸。相反,我们应该**扩展**现有的**自定义路由复用策略**,使其满足我们应用程序的要求。
首先,在**路由**配置中,我们需要添加一个标志,帮助我们识别哪些路径不应该被复用。在我们的例子中,就是**DetailComponent**。这可以通过在细节路径中添加data: { noReuse: true }
来实现。就像这样:
app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: '/items', pathMatch: 'full' },
{ path: 'items', component: ItemsComponent },
{ path: 'item/:id', component: ItemDetailComponent, data: { noReuse: true } },
];
然后,每次应用程序导航时,我们就可以查找noReuse
标志。
接下来,我们需要创建一个新的自定义路由复用策略类。
它应该扩展NSRouteReuseStrategy
export class CustomRouteReuseStrategy extends NSRouteReuseStrategy {
使用依赖注入获取NSLocationStrategy
,并将其传递给父构造函数
constructor(location: NSLocationStrategy) {
super(location);
}
最后,它应该实现shouldReuseRoute
函数,该函数应该返回true
或false
shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
// return (shouldIReuse) ? true : false;
}
这是我们需要专注于正确解决方案的地方。
我们**必须始终**从父级调用shouldReuseRoute()
,因为它用于**标记**页面特定的激活路由,并且结果将让我们知道它何时打算复用当前路由。
let shouldReuse = super.shouldReuseRoute(future, current);
如果shouldReuse
为 true,那么我们可能正处于从ItemDetailComponent
到自身重新导航的场景中。此时,我们需要检查noReuse
标志是否设置为true
。如果是,那么应用程序将从ItemDetailComponent
重新导航到自身。为了避免复用此路径,只需将shouldReuse
更改为false
。
if (shouldReuse && current.data.noReuse) {
shouldReuse = false;
}
最后,我们需要返回shouldReuse
的值。
return shouldReuse;
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot } from '@angular/router';
import { NSLocationStrategy } from 'nativescript-angular/router/ns-location-strategy';
import { NSRouteReuseStrategy } from 'nativescript-angular/router/ns-route-reuse-strategy';
@Injectable()
export class CustomRouteReuseStrategy extends NSRouteReuseStrategy {
constructor(location: NSLocationStrategy) {
super(location);
}
shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {
// first use the global Reuse Strategy evaluation function,
// which will return true, when we are navigating from the same component to itself
let shouldReuse = super.shouldReuseRoute(future, current);
// then check if the noReuse flag is set to true
if (shouldReuse && current.data.noReuse) {
// if true, then don't reuse this component
shouldReuse = false;
}
console.log(`Should Reuse: ${shouldReuse}`);
return shouldReuse;
}
}
最后一步是让 Angular 知道我们想要使用新的CustomRouteReuseStrategy
。
这可以通过将新的 CRRS 类作为RouteReuseStrategy
添加到AppModule
中来实现,就像这样:
app.module.ts
@NgModule({
...
providers: [
{
provide: RouteReuseStrategy,
useClass: CustomRouteReuseStrategy
}
],
...
})
你可以在Playground中查看完整的解决方案。
如你所见,实现自定义路由复用策略不仅是最佳解决方案,而且简单而优雅。你可以非常容易地将noReuse
标志添加到任何你不想复用的路径,同时保留其他所有路径的标志。
请在评论中告诉我们你的想法。
你以前遇到过这个挑战吗?
是否有任何这种情况没有涵盖?