Angular 守卫,文档中的表述不清楚

Angular guards, unclear statement in the docs

我想深入了解 angular,所以我阅读了 the docs,这对我很有帮助。
现在我正在研究守卫。我在文档中阅读了此声明。

The router checks the CanDeactivate and CanActivateChild guards first, from the deepest child route to the top. Then it checks the CanActivate guards from the top down to the deepest child route.

现在我很困惑,为什么angular会这样执行?
对于 CanDeactivateCanActivateChild,从最深的 child 到顶部进行检查有什么好处吗? CanActivate?

从顶部到最深的 child 路线

当您考虑路由时,您进入树越深,您得到的就越具体。

例如:

/food-types/sweets/pies/blueberry

因此,当您告诉 Angular 您想要离开 blueberry 饼图时,它会首先检查 CanDeactivate 蓝莓,因为您正沿着导航树往回走,以一个不同的位置。 CanActivateChild也会沿着树往上走,到子路径,据我理解,原因是一样的:先检查最深的层次,验证他们的子路径是否可以被激活。

CanActivate 则相反。当你告诉 Angular 你想看 blueberry 馅饼时,你正在沿着树走,因此,它会在沿着树走的时候按顺序检查守卫。

我曾试图相信文档站点上写的内容。但是,它似乎并不完全正确,或者实现已更新但文档没有更新。

简而言之:

首先,CanDeactivate 守卫从最深到顶部 检查,CanActivate 守卫从顶部到最深[=102] 检查=](它将退出并在遍历中进行虚假检查)。

其次,CanActivateChild 守卫没有从最深处到顶部进行检查。


TL;DR

详细说明

我们应该检查源代码以了解它是如何工作的。

Note: the commit checked is: https://github.com/angular/angular/tree/edb8375a5ff15d77709ccf1759efb14091fa86a4

第 1 步 - 查看 CanActivateChild 何时被呼叫

source here L929.

这只是其上级呼叫者 runCanActivateChild 被呼叫的地方。

在那一行,我们可以得到一些提示,它与 CanActivate 有相同的技巧,因为 CanActivate 的上级调用者 runCanActivate 在之后被调用。

第 2 步 - 查看 runCanActivateChild 是如何工作的

L926 and L950.

runCanActivateChildcanActivateChecks 的迭代中被调用,与 runCanActivate 的调用方式相同。这里我们知道 CanActivate(我指的是特征)和 CanActivateChild 共享相同的数据源 -- canActivateChecks.

第 3 步 - 什么是 canActivateChecks 及其处理方式

那么,什么是canActivateChecks?显然,我们可以发现它是一个 CanActivate class 个实例的数组。但是 canActivateChecks 是如何分配的呢? Go to here L865。这是重要的部分,所以我将它们粘贴在这里。

  private traverseChildRoutes(
      futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>|null,
      contexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
    const prevChildren = nodeChildrenAsMap(currNode);

    // Process the children of the future route
    futureNode.children.forEach(c => {
      this.traverseRoutes(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]));
      delete prevChildren[c.value.outlet];
    });

    // Process any children left from the current route (not active for the future route)
    forEach(
        prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) =>
                          this.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
  }

  private traverseRoutes(
      futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>,
      parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
    const future = futureNode.value;
    const curr = currNode ? currNode.value : null;
    const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;

    // reusing the node
    if (curr && future._routeConfig === curr._routeConfig) {
      if (this.shouldRunGuardsAndResolvers(
              curr, future, future._routeConfig !.runGuardsAndResolvers)) {
        this.canActivateChecks.push(new CanActivate(futurePath));
        const outlet = context !.outlet !;
        this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
      } else {
        // we need to set the data
        future.data = curr.data;
        future._resolvedData = curr._resolvedData;
      }

      // If we have a component, we need to go through an outlet.
      if (future.component) {
        this.traverseChildRoutes(
            futureNode, currNode, context ? context.children : null, futurePath);

        // if we have a componentless route, we recurse but keep the same outlet map.
      } else {
        this.traverseChildRoutes(futureNode, currNode, parentContexts, futurePath);
      }
    } else {
      // ##### comment by e-cloud #####
      if (curr) {
        this.deactivateRouteAndItsChildren(currNode, context);
      }

      this.canActivateChecks.push(new CanActivate(futurePath));
      // If we have a component, we need to go through an outlet.
      if (future.component) {
        this.traverseChildRoutes(futureNode, null, context ? context.children : null, futurePath);

        // if we have a componentless route, we recurse but keep the same outlet map.
      } else {
        this.traverseChildRoutes(futureNode, null, parentContexts, futurePath);
      }
    }
  }

有点长。但是如果你仔细研究它,你会发现它播放 depth-first-traversal。让我们忽略相同的路由切换。找到 ##### comment by e-cloud ##### 并查看主要过程。它表明它先更新canActivateChecks然后执行下一级遍历(整个Pre-order遍历)。

你一定知道路由器把app的所有路由都当做一棵url树。每个PreActivation通过遍历将其future(作为树路径)分成路径段

举一个简化的例子:

we have the future route as /a/b/c.
Then we will get [ '/a', '/a/b', '/a/b/c' ] as canActivateChecks

显然,canActivateChecks代表了future中从顶部到最深处的路线 来源显示 canActivateChecks 从左到右迭代。

第 4 步 - 结论

我们可以得出结论,CanActivateChild从上到下是运行child。

希望我解释清楚。