dojo dragon main logo

Outlet

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

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

-------------------------------------------------------------------
|        |                                            |           |
|        |                                            |           |
|        |                                            |           |
|        |                                            |           |
|        |                                            |           |
|  menu  |                   main                     | side-menu |
|        |                                            |           |
|        |                                            |           |
|        |                                            |           |
|        |                                            |           |
|        |                                            |           |
-------------------------------------------------------------------

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

const routes = [
	{
		id: 'landing',
		path: '/',
		outlet: 'main',
		defaultRoute: true
	},
	{
		id: 'widget',
		path: 'widget/{widget}',
		outlet: 'side-menu',
		children: [
			{
				id: 'tests',
				path: 'tests',
				outlet: 'main'
			},
			{
				id: 'overview',
				path: 'overview',
				outlet: 'main'
			},
			{
				id: 'example'
				path: 'example/{example}',
				outlet: 'main'
			}
		]
	}
];

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

import { create, tsx } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';

import Menu from './Menu';
import SideMenu from './SideMenu';
import Landing from './Landing';
import Tests from './Tests';
import Example from './Example';

const factory = create();

const App = factory(function App() {
	return (
		<div>
			<Menu />
			<main>
				<div>
					<Outlet id="main">
						{{
							landing: <Landing />,
							tests: <Tests />,
							example: ({ params: { example }}) => <Example example={example}/>,
							overview: <Example example="overview"/>
						}}
					</Outlet>
				</div>
				<div>
					<Outlet id="side-menu">
						{({ params: { widget }}) => <SideMenu widget={widget}>}
					</Outlet>
				</div>
			</main>
		</div>
	);
});

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

import { create, tsx } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';

import Menu from './Menu';
import SideMenu from './SideMenu';
import Landing from './Landing';
import Tests from './Tests';
import Example from './Example';

const factory = create();

const App = factory(function App() {
	return (
		<div>
			<Menu />
			<main>
				<div>
					<Outlet id="main" matcher={(defaultMatches, matchDetailsMap) => {
						defaultMatches.details = matchDetailsMap.has('example') || matchDetailsMap.has('overview');
						return defaultMatches;
					}}>
						{{
							landing: <Landing />,
							tests: <Tests />,
							details: ({ params: { example = "overview" }}) => <Example example={example}/>,
						}}
					</Outlet>
				</div>
				<div>
					<Outlet id="side-menu">
						{({ params: { widget }}) => <SideMenu widget={widget}>}
					</Outlet>
				</div>
			</main>
		</div>
	);
});