Dojo 路由

路由

dojo/framework/src/routing/README.md

commit b682b06ace25eea86d190e56dd81042565b35ed1

Dojo 应用程序的路由

Features

部件(Widget)是 Dojo 应用程序的基本概念,因此 Dojo 路由提供了一组与应用程序中的部件直接集成的组件。这些组件能将部件注册到路由上,且不需要掌握路由相关的任何知识。Dojo 应用程序中的路由包括以下内容:

  • Outlet 部件封装器指定 route 的 outlet key 和表现视图之间的映射关系
  • Route 配置,用于在路径和 outlet key 之间建立映射关系
  • Router 基于当前路径解析 Route
  • History 提供器负责向 Router 通知路径的更改
  • RegistryRouter 注入到部件系统中

Route 配置

RouteConfig 用于注册应用程序的路由,它定义了路由的 path、关联的 outlet 以及嵌套的子 RouteConfig。一个完整的路由就是由递归嵌套的 Route 构成的。

路由配置示例:

import { RouteConfig } from '@dojo/framework/routing/interfaces';

const config: RouteConfig[] = [
    {
        path: 'foo',
        outlet: 'root',
        children: [
            {
                path: 'bar',
                outlet: 'bar'
            },
            {
                path: 'baz',
                outlet: 'baz',
                children: [
                    {
                        path: 'qux',
                        outlet: 'qux'
                    }
                ]
            }
        ]
    }
];

此配置将注册以下路由和 outlet:

Route Outlet
/foo root
/foo/bar bar
/foo/baz baz
/foo/baz/qux qux

路径参数

RouteConfig 的 path 属性中,在 path 的值中使用大括号可定义路径参数。 Parameters will match any segment and the value of that segment is made available to matching outlets via the mapParams Outlet options. The parameters provided to child outlets will include any parameters from matching parent routes.

const config = [
    {
        path: 'foo/{foo}',
        outlet: 'foo'
    }
];

具有路径参数的路由,可为每个路由指定默认的参数。当用没有指定参数的 outlet 生成一个链接,或者当前路由中不存在参数时,可使用这些默认参数值。

const config = [
    {
        path: 'foo/{foo}',
        outlet: 'foo',
        defaultParams: {
            foo: 'bar'
        }
    }
];

可使用可选的配属属性 defaultRoute 来设置默认路由,如果当前路由没有匹配到已注册的路由,就使用此路由。

const config = [
    {
        path: 'foo/{foo}',
        outlet: 'foo',
        defaultRoute: true
    }
];

Router

Router 用于注册 route 配置,将 route 配置信息传入 Router 的构造函数即可:

const router = new Router(config);

会自动为 router 注册一个 HashHistory 历史管理器(history manager)。可在第二个参数中传入其他历史管理器。

import { MemoryHistory } from '@dojo/framework/routing/MemoryHistory';

const router = new Router(config, { HistoryManager: MemoryHistory });

使用应用程序路由配置创建路由后,需要让应用程序中的所有组件可使用这些路由。这是通过使用 @dojo/framework/widget-core/Registry 中的 Registry,定义一个将 invalidator 连接到 router 的 nav 事件的注入器,并返回 router 实例实现的。这里使用 key 来定义注入器,路由器的默认 key 值为 router

import { Registry } from '@dojo/framework/widget-core/Registry';
import { Injector } from '@dojo/framework/widget-core/Injector';

const registry = new Registry();

// 假设我们有一个可用的 router 实例
registry.defineInjector('router', () => {
    router.on('nav', () => invalidator());
    return () => router;
};

注意: 路由提供了 注册 router 的快捷方法

最后,为了让应用程序中的所有部件都能使用 registry,需要将其传给 vdom renderer.mount() 方法。

const r = renderer(() => v(App, {}));
r.mount({ registry });

History Managers

路由自带三个历史管理器,用于监视和更改导航状态:HashHistoryStateHistoryMemoryHistory。默认使用 HashHistory,但是可在创建 Router 时传入不同的 HistoryManager

const router = new Router(config, { HistoryManager: MemoryHistory });
Hash History

基于哈希的管理器使用片段标识符(fragment identifier)来存储导航状态,是 @dojo/framework/routing 中的默认管理器。

import { Router } from '@dojo/framework/routing/Router';
import { HashHistory } from '@dojo/framework/routing/history/HashHistory';

const router = new Router(config, { HistoryManager: HashHistory });

历史管理器有 currentset(path: string)prefix(path: string) 三个API。HashHistory 类假定全局对象是浏览器的 window 对象,但可以显式提供对象。管理器使用 window.location.hashhashchange 事件的事件监听器。current 访问器返回当前路径,不带 # 前缀。

State History

基于状态的历史管理器使用浏览器的 history API:pushState()replaceState(),来添加和修改历史纪录。状态历史管理器需要服务器端支持才能有效工作。

Memory History

MemoryHistory 不依赖任何浏览器 API,而是保持其自身的内部路径状态。不要在生产应用程序中使用它,但在测试路由时却很有用。

import { Router } from '@dojo/framework/routing/Router';
import { MemoryHistory } from '@dojo/framework/routing/history/MemoryHistory';

const router = new Router(config, { HistoryManager: MemoryHistory });

Outlet Event

当每次进入或离开 outlet 时,都会触发 router 实例的 outlet 事件。outlet 上下文、enterexit 操作都会传给事件处理函数。

router.on('outlet', ({ outlet, action }) => {
    if (action === 'enter') {
        if (outlet.id === 'my-outlet') {
            // do something, perhaps fetch data or set state
        }
    }
});

Router Context Injection

RouterInjector 模块导出一个帮助函数 registerRouterInjector,它组合了 Router 实例的实例化,注册 router 配置信息和为提供的 registry 定义注入器,然后返回 router 实例。

import { Registry } from '@dojo/framework/widget-core/Registry';
import { registerRouterInjector } from '@dojo/framework/routing/RoutingInjector';

const registry = new Registry();
const router = registerRouterInjector(config, registry);

可使用 RouterInjectiorOptions 覆盖默认值:

import { Registry } from '@dojo/framework/widget-core/Registry';
import { registerRouterInjector } from '@dojo/framework/routing/RoutingInjector';
import { MemoryHistory } from './history/MemoryHistory';

const registry = new Registry();
const history = new MemoryHistory();

const router = registerRouterInjector(config, registry, { history, key: 'custom-router-key' });

Outlets

路由集成的一个基本概念是 outlet,它是与注册的应用程序 route 关联的唯一标识符。Outlet 是一个标准的 dojo 部件,可在应用程序的任何地方使用。Outlet 部件有一个精简的 API:

  • id: 匹配时执行 renderer 的 outlet 标识。
  • renderer: 当 outlet 匹配时调用的渲染函数。
  • routerKey (可选): 在 registry 中定义路由时使用的 key - 默认为 router

接收渲染的 outlet 名称和一个 renderer 函数,当 outlet 匹配时,该函数返回要渲染的 DNode

render() {
    return v('div', [
        w(Outlet, { id: 'my-outlet', renderer: () => {
            return w(MyWidget, {});
        }})
    ])
}

可为 renderer 函数传入 MatchDetails 参数,该参数提供路由专有信息,用于确定要渲染的内容和计算传入部件的属性值。

interface MatchDetails {
    /**
     * Query params from the matching route for the outlet
     */
    queryParams: Params;

    /**
     * Params from the matching route for the outlet
     */
    params: Params;

    /**
     * Match type of the route for the outlet, either `index`, `partial` or `error`
     */
    type: MatchType;

    /**
     * The router instance
     */
    router: RouterInterface;

    /**
     * Function returns true if the outlet match was an `error` type
     */
    isError(): boolean;

    /**
     * Function returns true if the outlet match was an `index` type
     */
    isExact(): boolean;
}
render() {
    return v('div', [
        w(Outlet, { id: 'my-outlet', renderer: (matchDetails: MatchDetails) => {
            if (matchDetails.isError()) {
                return w(ErrorWidget, {});
            }
            if (matchDetails.isExact()) {
                return w(IndexWidget, { id: matchDetails.params.id });
            }
            return w(OtherWidget, { id: matchDetails.params.id });
        }})
    ])
}

Global Error Outlet

只要注册了一个 error 匹配类型,就会自动为匹配的 outlet 添加一个全局 outlet,名为 errorOutlet。这个 outlet 用于为任何未知的路由渲染一个部件。

render() {
    return w(Outlet, {
        id: 'errorOutlet',
        renderer: (matchDetails: MatchDetails) => {
            return w(ErrorWidget, properties);
        }
    });
}

Link

Link 组件是对 DOM 节点 a 的封装,允许用户为创建的链接指定一个 outlet。也可以通过将 isOutlet 属性值设置为 false 来使用静态路由。

如果生成的链接需要指定在 route 中不存在的路径或查询参数,可使用 params 属性传入。

import { Link } from '@dojo/framework/routing/Link';

render() {
    return v('div', [
        w(Link, { to: 'foo', params: { foo: 'bar' }}, [ 'Link Text' ]),
        w(Link, { to: '#/static-route', isOutlet: false, [ 'Other Link Text' ])
    ]);
}

所有标准的 VNodeProperties 都可用于 Link 组件,因为最终是使用 @dojo/framework/widget-core 中的 v() 来创建一个 a 元素。

ActiveLink

ActiveLink 组件是对 Link 组件的封装,如果链接处于激活状态,则可以为 a 节点设置样式类。

import { ActiveLink } from '@dojo/framework/routing/ActiveLink';

render() {
    return v('div', [
        w(ActiveLink, { to: 'foo', params: { foo: 'bar' }, activeClasses: [ 'link-active' ]}, [ 'Link Text' ])
    ]);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容

  • 版权声明:原创作品,谢绝转载!否则将追究法律责任。 OSPF路由协议 楔子 为了加强自己我打算好好玩玩网络。 OS...
    李伟铭MIng阅读 4,565评论 0 25
  • 前言 react-router针对不同的使用场景衍生了不同的路由包,RN项目用react-router-nativ...
    大柚子08阅读 36,476评论 5 39
  • 路由要解决的核心问题是通过建立URL和页面的对应关系,使得不同的页面可以用不同的URL表示。在angular中,页...
    oWSQo阅读 1,291评论 0 1
  • 1. 三大性质简介 在并发编程中分析线程安全的问题时往往需要切入点,那就是两大核心:JMM抽象内存模型以及happ...
    朦胧蜜桃阅读 335评论 0 0
  • 听着熟悉的情歌 又开始期待爱情 伴着一群人的孤独 写下这段心语 道出悲哀的疑问 是什么让我们 美好年华却没有牵手的...
    子慕清阅读 192评论 1 3