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>
    );
});