Ember 中的路由

在本文中,我们将学习路由,有时也称作基于 URL 的过滤。我们将使用它为三个待办事项视图(“所有”、“活动”和“已完成”)提供一个唯一的 URL。

预备知识

至少,建议您熟悉核心 HTMLCSSJavaScript 语言,并了解 终端/命令行

Ember 大量使用现代 JavaScript 特性(如类、模块等),因此深入理解这些特性将非常有益。

目标 学习在 Ember 中实现路由。

基于 URL 的过滤

Ember 提供了一个与浏览器 URL 紧密集成的路由系统。通常,在编写 Web 应用程序时,您希望页面由 URL 表示,这样如果(出于任何原因)页面需要刷新,用户不会对 Web 应用程序的状态感到惊讶 — 他们可以直接链接到应用程序的重要视图。

目前,我们已经有了“所有”页面,因为我们当前正在处理的页面没有进行任何过滤,但我们需要对其进行一些重组,以处理“活动”和“已完成”待办事项的不同视图。

Ember 应用程序有一个默认的“application”路由,它与 app/templates/application.hbs 模板关联。由于该应用程序模板是我们待办事项应用程序的入口点,我们需要进行一些更改以允许路由。

创建路由

让我们从创建三个新路由开始:“Index”、“Active”和“Completed”。为此,您需要在应用程序的根目录中,在终端中输入以下命令

bash
ember generate route index
ember generate route completed
ember generate route active

第二个和第三个命令不仅应该生成了新文件,还更新了一个现有文件 app/router.js。它包含以下内容

js
import EmberRouter from "@ember/routing/router";
import config from "./config/environment";

export default class Router extends EmberRouter {
  location = config.locationType;
  rootURL = config.rootURL;
}

Router.map(function () {
  this.route("completed");
  this.route("active");
});

高亮行是在运行上面第二个和第三个命令时添加的。

router.js 充当开发人员的“站点地图”,以便能够快速查看整个应用程序的结构。它还告诉 Ember 如何与您的路由进行交互,例如在加载任意数据、处理加载数据时的错误或解释 URL 的动态片段时。由于我们的数据是静态的,我们不会接触到那些高级功能,但我们仍然会确保路由提供查看页面所需的最少数据。

创建“Index”路由时没有向 router.js 添加路由定义行,因为与 URL 导航和 JavaScript 模块加载一样,“Index”是一个特殊词,表示要渲染、加载等的默认路由。

为了调整我们旧的渲染 TodoList 应用程序的方式,我们首先需要将应用程序模板中的 TodoList 组件调用替换为 {{outlet}} 调用,这意味着“任何子路由都将在此处渲染”。

转到 todomvc/app/templates/application.hbs 文件,并将

hbs
<TodoList />

替换为

hbs
{{outlet}}

接下来,在我们的 index.hbscompleted.hbsactive.hbs 模板(也在模板目录中)中,我们现在可以只输入 TodoList 组件调用。

在每种情况下,替换

hbs
{{outlet}}

with

hbs
<TodoList />

因此,此时,如果您再次尝试该应用程序并访问这三个路由中的任何一个

localhost:4200 localhost:4200/active localhost:4200/completed

您将看到完全相同的内容。在每个 URL 中,与特定路径(“Active”、“Completed”或“Index”)对应的模板将渲染 <TodoList /> 组件。页面中 <TodoList /> 的渲染位置由父路由中的 {{ outlet }} 决定,在本例中是 application.hbs。所以我们的路由已经就位。太棒了!

但现在我们需要一种方法来区分这些路由,以便它们显示它们应该显示的内容。

首先,再次返回到我们的 todo-data.js 文件。它已经包含一个返回所有待办事项的 getter,以及一个返回未完成待办事项的 getter。我们缺少的一个 getter 是返回已完成待办事项的。在现有 getter 下方添加以下内容

js
export default class TodoDataService extends Service {
  // …
  get completed() {
    return this.todos.filter((todo) => todo.isCompleted);
  }
  // …
}

模型

现在我们需要将模型添加到我们的路由 JavaScript 文件中,以便我们可以轻松地返回特定的数据集以在这些模型中显示。model 是一个数据加载生命周期钩子。对于 TodoMVC,模型的功能对我们来说并不那么重要;如果您想深入了解,可以在 Ember 模型指南 中找到更多信息。我们还提供了对服务的访问,就像我们对组件所做的那样。

索引路由模型

首先,更新 todomvc/app/routes/index.js,使其看起来如下所示

js
import Route from "@ember/routing/route";
import { inject as service } from "@ember/service";

export default class IndexRoute extends Route {
  @service("todo-data") todos;

  model() {
    let todos = this.todos;

    return {
      get allTodos() {
        return todos.all;
      },
    };
  }
}

我们现在可以更新 todomvc/app/templates/index.hbs 文件,以便在它包含 <TodoList /> 组件时,它明确地使用可用模型,调用其 allTodos() getter,以确保显示所有待办事项。

在此文件中,更改

hbs
<TodoList />

hbs
<TodoList @todos={{ @model.allTodos }} />

已完成路由模型

现在更新 todomvc/app/routes/completed.js,使其看起来如下所示

js
import Route from "@ember/routing/route";
import { inject as service } from "@ember/service";

export default class CompletedRoute extends Route {
  @service("todo-data") todos;

  model() {
    let todos = this.todos;

    return {
      get completedTodos() {
        return todos.completed;
      },
    };
  }
}

我们现在可以更新 todomvc/app/templates/completed.hbs 文件,以便在它包含 <TodoList /> 组件时,它明确地使用可用模型,调用其 completedTodos() getter,以确保只显示已完成的待办事项。

在此文件中,更改

hbs
<TodoList />

hbs
<TodoList @todos={{ @model.completedTodos }} />

活动路由模型

最后对于路由,让我们整理我们的活动路由。首先更新 todomvc/app/routes/active.js,使其看起来如下所示

js
import Route from "@ember/routing/route";
import { inject as service } from "@ember/service";

export default class ActiveRoute extends Route {
  @service("todo-data") todos;

  model() {
    let todos = this.todos;

    return {
      get activeTodos() {
        return todos.incomplete;
      },
    };
  }
}

我们现在可以更新 todomvc/app/templates/active.hbs 文件,以便在它包含 <TodoList /> 组件时,它明确地使用可用模型,调用其 activeTodos() getter,以确保只显示活动的(未完成的)待办事项。

在此文件中,更改

hbs
<TodoList />

hbs
<TodoList @todos={{ @model.activeTodos }} />

请注意,在每个路由模型钩子中,我们都返回一个带有 getter 的对象,而不是一个静态对象,或者仅仅是待办事项的静态列表(例如 this.todos.completed)。这样做的原因是,我们希望模板对待办事项列表有一个动态引用,如果我们直接返回列表,数据将永远不会重新计算,这将导致导航看起来失败/实际上没有过滤。通过在模型数据的返回对象中定义一个 getter,待办事项会重新查找,以便我们对 todo 列表的更改反映在渲染列表中。

所以我们的路由功能现在都已就位,但我们无法从我们的应用程序中访问它们。让我们激活页脚链接,以便点击它们可以转到所需的路由。

回到 todomvc/app/components/footer.hbs,找到以下标记

hbs
<a href="#">All</a>
<a href="#">Active</a>
<a href="#">Completed</a>

将其更新为

hbs
<LinkTo @route="index">All</LinkTo>
<LinkTo @route="active">Active</LinkTo>
<LinkTo @route="completed">Completed</LinkTo>

<LinkTo> 是一个内置的 Ember 组件,它处理导航路由时的所有状态更改,并为任何与 URL 匹配的链接设置一个“active”类,以防需要将其与非活动链接进行不同样式设置。

更新 TodoList 中的待办事项显示

我们需要修复的最后一个小问题是,以前在 todomvc/app/components/todo-list.hbs 中,我们直接访问 todo-data 服务并循环遍历所有待办事项,如下所示

hbs
{{#each this.todos.all as |todo| }}

由于我们现在希望 TodoList 组件显示一个过滤列表,我们将需要向 TodoList 组件传递一个表示“当前待办事项列表”的参数,如下所示

hbs
{{#each @todos as |todo| }}

本次教程到此结束!您的应用程序现在应该在页脚中拥有完全可用的链接,可以显示“Index”/默认、“Active”和“Completed”路由。

The todo list app, showing the routing working for all, active, and completed todos.

总结

恭喜!您已完成本教程!

在与原始 TodoMVC 应用程序 功能保持一致之前,我们还需要实现更多功能,例如编辑、删除和在页面重新加载时保留待办事项。

要查看我们完成的 Ember 实现,请查看 本教程代码 仓库中的已完成应用程序文件夹,或在此处查看 实时部署版本。学习代码以了解更多关于 Ember 的信息,并查看下一篇文章,其中提供了更多资源链接和一些故障排除建议。