Outlet

outlet 用于标注应用程序中的显示位置,在这个位置可以根据匹配到的路由渲染不同的内容。与使用路由相比,使用 outlet 能减少编写样板代码,将多个路由关联到同一个 outlet 下,以便更自然、更准确的描述应用程序的输出结构。

考虑一个经典的应用程序布局,其中包括一个左侧菜单和主内容视图,在主内容视图右侧有一个根据路由变化的菜单栏:

  1. -------------------------------------------------------------------
  2. | | | |
  3. | | | |
  4. | | | |
  5. | | | |
  6. | | | |
  7. | menu | main | side-menu |
  8. | | | |
  9. | | | |
  10. | | | |
  11. | | | |
  12. | | | |
  13. -------------------------------------------------------------------

以下的路由配置将所有的主页面指向显示主内容的 outlet,将 widget 指向显示 side-menu 的 outlet。便可以构建出这样一个应用程序,它始终根据路由动态渲染主内容区,也会根据右侧菜单栏中 widget 路由的所有子路由动态渲染主内容区。

  1. const routes = [
  2. {
  3. id: 'landing',
  4. path: '/',
  5. outlet: 'main',
  6. defaultRoute: true
  7. },
  8. {
  9. id: 'widget',
  10. path: 'widget/{widget}',
  11. outlet: 'side-menu',
  12. children: [
  13. {
  14. id: 'tests',
  15. path: 'tests',
  16. outlet: 'main'
  17. },
  18. {
  19. id: 'overview',
  20. path: 'overview',
  21. outlet: 'main'
  22. },
  23. {
  24. id: 'example'
  25. path: 'example/{example}',
  26. outlet: 'main'
  27. }
  28. ]
  29. }
  30. ];

在上面的路由配置中,定义了两个 outlet,mainside-menu,下面显示了使用这两个 outlet 的简化版应用程序布局。默认情况下,Outlet 将会渲染 key 值与 outlet 下的路由 id 匹配的路由,如本例中的 main。如果为 Outlet 传入的是一个函数,则与 outlet 中的 任一 路由匹配时,都会渲染。

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import Outlet from '@dojo/framework/routing/Outlet';
  3. import Menu from './Menu';
  4. import SideMenu from './SideMenu';
  5. import Landing from './Landing';
  6. import Tests from './Tests';
  7. import Example from './Example';
  8. const factory = create();
  9. const App = factory(function App() {
  10. return (
  11. <div>
  12. <Menu />
  13. <main>
  14. <div>
  15. <Outlet id="main">
  16. {{
  17. landing: <Landing />,
  18. tests: <Tests />,
  19. example: ({ params: { example }}) => <Example example={example}/>,
  20. overview: <Example example="overview"/>
  21. }}
  22. </Outlet>
  23. </div>
  24. <div>
  25. <Outlet id="side-menu">
  26. {({ params: { widget }}) => <SideMenu widget={widget}>}
  27. </Outlet>
  28. </div>
  29. </main>
  30. </div>
  31. );
  32. });

App 的节点结构看起来很直观,简洁的描述出实际视觉输出,只是有一小处重复,即仍然需要在不同路由中重复使用 Example 部件。这可以通过使用 matcher 属性来覆盖默认的路由匹配规则来解决。matcher 会接收 defaultMatchesmatchDetailsMap 两个参数,以便自定义匹配策略。在下面最后一个示例中,将重复使用的 Example 合并为一个新 key,即 details,但在路由定义中并不存在。如果不覆写默认匹配,让匹配到 exampleoverview 路由时将其设置为 true,则此 outlet 永远不会匹配到 details。最后,在 details 渲染函数中,将 example 属性的默认值设置为 overview,以与之前的行为保持一致。

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import Outlet from '@dojo/framework/routing/Outlet';
  3. import Menu from './Menu';
  4. import SideMenu from './SideMenu';
  5. import Landing from './Landing';
  6. import Tests from './Tests';
  7. import Example from './Example';
  8. const factory = create();
  9. const App = factory(function App() {
  10. return (
  11. <div>
  12. <Menu />
  13. <main>
  14. <div>
  15. <Outlet id="main" matcher={(defaultMatches, matchDetailsMap) => {
  16. defaultMatches.details = matchDetailsMap.has('example') || matchDetailsMap.has('overview');
  17. return defaultMatches;
  18. }}>
  19. {{
  20. landing: <Landing />,
  21. tests: <Tests />,
  22. details: ({ params: { example = "overview" }}) => <Example example={example}/>,
  23. }}
  24. </Outlet>
  25. </div>
  26. <div>
  27. <Outlet id="side-menu">
  28. {({ params: { widget }}) => <SideMenu widget={widget}>}
  29. </Outlet>
  30. </div>
  31. </main>
  32. </div>
  33. );
  34. });