Skip to content
Mithril.js 2
Main Navigation 指南API

简体中文

English
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

简体中文

English
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

主题

Sidebar Navigation

API

核心 API

m(selector, attributes, children)

render(element, vnodes)

mount(root, component)

route(root, defaultRoute, routes)

request(options)

parseQueryString(string)

buildQueryString(object)

buildPathname(object)

parsePathname(string)

trust(html)

fragment(attrs, children)

redraw()

censor(object, extra)

可选 API

stream()

指南

页面导航

route(root, defaultRoute, routes) ​

描述 ​

在应用程序的不同“页面”之间进行导航。

javascript
var Home = {
  view: function () {
    return 'Welcome';
  },
};

m.route(document.body, '/home', {
  '/home': Home, // 定义 `https://localhost/#!/home`
});

每个应用只能调用一次 m.route。

签名 ​

m.route(root, defaultRoute, routes)

参数类型必需描述
rootElement是作为子树父节点的 DOM 元素。
defaultRouteString是如果当前 URL 不匹配任何路由,则重定向到该路由。注意:这并非初始路由,初始路由是浏览器地址栏中的 URL。
routesObject<String,Component|RouteResolver>是一个对象,其键为路由字符串,值为组件或 RouteResolver。
返回值无返回值。

如何阅读签名

静态成员 ​

m.route.set ​

重定向到匹配的路由,如果找不到匹配的路由,则重定向到默认路由。触发所有挂载点的异步重绘。

m.route.set(path, params, options)

参数类型必需描述
pathString是要跳转到的路径名,不包含前缀。路径中可以包含参数,这些参数会使用 params 中的值进行替换。
paramsObject否路由参数。如果 path 包含路由参数占位符,则此对象的属性值将替换路径字符串中的占位符。
options.replaceBoolean否是否创建新的历史记录条目或替换当前条目。默认为 false。
options.stateObject否传递给底层 history.pushState / history.replaceState 调用的 state 对象。该对象会保存在 history.state 属性中,并合并到路由参数对象中。注意:只有在使用 pushState API 时,此选项才有效;如果路由器回退到 hashchange 模式(例如,pushState API 不可用),此选项会被忽略。
options.titleString否要传递给底层 history.pushState / history.replaceState 调用的 title 字符串。
返回值无返回值。

请注意,使用 .set 和 params 时,还需要定义路由:

javascript
var Article = {
  view: function (vnode) {
    return 'This is article ' + vnode.attrs.articleid; // 显示文章ID:  + vnode.attrs.articleid
  },
};

m.route(document.body, {
  '/article/:articleid': Article,
});
m.route.set('/article/:articleid', { articleid: 1 });

m.route.get ​

返回最后一个完全解析的路由路径,不带前缀。在异步路由等待解析时,该路径可能与地址栏中显示的路径不同。

path = m.route.get()

参数类型必需描述
返回值String返回最后一个完全解析的路由路径。

m.route.prefix ​

定义路由前缀。路由器前缀是 URL 的一部分,它决定了路由器使用的底层 策略。

m.route.prefix = prefix

参数类型必需描述
prefixString是用于控制 Mithril 底层路由策略的前缀。

这是一个普通属性,因此您可以读取和写入它。

m.route.Link ​

该组件用于创建动态路由链接。主要功能是生成 <a> 链接,并根据路由前缀转换本地 href 属性。

javascript
m(m.route.Link, { href: '/foo' }, 'foo');

// 除非 m.route.prefix 已从默认策略更改,否则渲染为:
// <a href="#!/foo">foo</a>

链接接受一些特殊属性:

  • selector:将作为第一个参数传递给 m 的内容:任何选择器都有效,包括非 a 元素。
  • params & options:与 m.route.set 中定义的名称相同的参数。
  • disabled:若设为 true 则禁用路由行为和任何绑定的 onclick 处理程序,并附加一个 data-disabled="true" 属性以提供辅助功能提示;如果元素是 a,则删除 href。

不能通过事件处理 API 阻止路由行为,请使用 disabled 属性。

javascript
m(
  m.route.Link,
  {
    href: '/foo',
    selector: 'button.large',
    disabled: true,
    params: { key: 'value' },
    options: { replace: true },
  },
  'link name'
);

// 渲染为:
// <button disabled aria-disabled="true" class="large">link name</button>

vnode = m(m.route.Link, attributes, children)

参数类型必需描述
attributes.hrefObject是要导航到的目标路由。
attributes.disabledBoolean否以无障碍的方式禁用该元素。
attributes.selectorString|Object|Function否m 的选择器,默认为 "a"。
attributes.optionsObject否设置传递给 m.route.set 的 options。
attributes.paramsObject否设置传递给 m.route.set 的 params。
attributesObject否要转发到 m 的任何其他属性。
childrenArray<Vnode>|String|Number|Boolean否此链接的子 vnode。
返回值Vnode一个 vnode。

m.route.param ​

从最近一次完全解析的路由中获取路由参数。路由参数可能来源于以下几个地方:

  • 路由参数替换(例如,如果路由是 /users/:id,并且它解析为 /users/1,则路由参数具有键 id 和值 "1")
  • 路由器查询字符串(例如,如果路径是 /users?page=1,则路由参数具有键 page 和值 "1")
  • history.state(例如,如果 history.state 是 {foo: "bar"},则路由参数具有键 foo 和值 "bar")

value = m.route.param(key)

参数类型必需描述
keyString否路由参数名称(例如,路由 /users/:id 中的 id,或路径 /users/1?page=3 中的 page,或 history.state 中的键)。
返回值String|Object返回指定键对应的值。如果未指定键,则返回包含所有键值对的对象。

请注意,在 RouteResolver 的 onmatch 函数中,新路由尚未完全解析,并且 m.route.param() 将返回先前路由的参数(如果有)。onmatch 接收新路由的参数作为参数。

m.route.SKIP ​

一个特殊的值,可以从路由解析器的 onmatch中返回,用于跳过当前路由,进入下一个路由。

RouteResolver ​

RouteResolver 是一个非组件对象,它包含 onmatch 方法和/或 render 方法。这两个方法都是可选的,但至少要存在一个。

如果一个对象可以被检测为组件(通过存在 view 方法或通过作为 function/class),即使它具有 onmatch 或 render 方法,它也会被视为组件。由于 RouteResolver 不是组件,因此它没有生命周期方法。

通常,RouteResolver 应该与 m.route 调用放在同一个文件中,而组件定义应该放在单独的模块中。

routeResolver = {onmatch, render}

当使用组件时,您可以将它们视为此路由解析器的特殊语法糖,假设您的组件是 Home:

javascript
var routeResolver = {
  onmatch: function () {
    return Home;
  },
  render: function (vnode) {
    return [vnode];
  },
};

routeResolver.onmatch ​

当路由器需要找到要渲染的组件时,会调用 onmatch 钩子。每次路由路径改变时,该钩子都会被调用一次;但在同一路径上的后续重绘过程中,不会被调用。它可用于在组件初始化之前运行逻辑(例如身份验证逻辑、数据预加载、重定向分析跟踪等)。

该方法还允许异步定义要渲染的组件,使其适用于代码分割和异步模块加载。要异步渲染组件,请返回一个解析为组件的 promise。

有关 onmatch 的更多信息,请参阅 高级组件解析 部分。

routeResolver.onmatch(args, requestedPath, route)

参数类型描述
argsObject路由参数。
requestedPathString上次路由操作请求的路由器路径,包括插值的路由参数值,但不包括前缀。在 onmatch 被调用时,当前路径的解析尚未完成,m.route.get() 仍然会返回之前的路径。
routeString上次路由操作请求的路由器路径,不包括插值的路由参数值。
返回值Component|\Promise<Component>|undefined返回一个组件或一个解析为组件的 promise。

如果 onmatch 返回一个组件或一个解析为组件的 promise,则此组件用作 RouteResolver 的 render 方法中第一个参数的 vnode.tag。否则,vnode.tag 设置为 "div"。类似地,如果省略 onmatch 方法,则 vnode.tag 也为 "div"。

如果 onmatch 返回一个被拒绝的 promise,则路由器将重定向回 defaultRoute。您可以通过在返回 promise 链之前调用 .catch 来覆盖此行为。

routeResolver.render ​

对于匹配的路由,每次重绘都会调用 render 方法。它类似于组件中的 view 方法,它的存在是为了简化 组件组合。它还允许你避免 Mithril.js 默认替换整个子树的行为。

vnode = routeResolver.render(vnode)

参数类型描述
vnodeObject一个 vnode,其属性对象包含路由参数。如果 onmatch 不返回组件或解析为组件的 promise,则 vnode 的 tag 字段默认为 "div"。
vnode.attrsObjectURL 参数值的映射。
返回值Array<Vnode>|Vnode要渲染的 vnode。

vnode 参数只是 m(Component, m.route.param()),其中 Component 是路由的已解析组件(在 routeResolver.onmatch 之后),m.route.param() 如 此处 所述。如果省略该方法,默认返回值是 [vnode],会被包裹在一个 Fragment 中,以便使用键参数。与 :key 参数结合使用,它将成为一个 单元素键控片段,因为它最终会渲染成类似 [m(Component, {key: m.route.param("key"), ...})] 的东西。

工作原理 ​

路由是一种用于创建单页应用程序 (SPA) 的机制,它允许在不同的“页面”之间切换,而无需刷新整个浏览器。

它实现了无缝导航,同时保留了单独为每个页面添加书签的能力,以及通过浏览器的历史记录机制导航应用程序的能力。

在不刷新页面的情况下进行路由部分地由 history.pushState API 实现。使用此 API,可以在页面加载后以编程方式更改浏览器显示的 URL,但应用程序开发人员有责任确保从初始状态(例如,新选项卡)导航到任何给定的 URL 将渲染适当的标记。

路由策略 ​

路由策略决定了库如何实现路由。有三种通用策略可用于实现 SPA 路由系统,每种策略都有不同的注意事项:

  • m.route.prefix = '#!'(默认)– 使用 URL 的 片段标识符(也称为哈希)部分。使用此策略的 URL 通常看起来像 https://localhost/#!/page1。
  • m.route.prefix = '?' – 使用查询字符串。使用此策略的 URL 通常看起来像 https://localhost/?/page1。
  • m.route.prefix = '' – 使用路径名。使用此策略的 URL 通常看起来像 https://localhost/page1。

哈希策略保证在不支持 history.pushState 的浏览器中也能正常工作,因为它能回退到使用 onhashchange 事件。如果希望哈希仅在本地生效,请使用此策略。

查询字符串策略允许服务器端进行检测,但 URL 看起来不像常规路径。如果需要支持服务器端锚点链接检测,并且无法修改服务器配置以支持路径名策略(例如,使用 Apache 且无法修改 .htaccess 文件),请使用此策略。

路径名策略可以生成最简洁的 URL,但需要配置服务器,以便从应用程序可以路由的每个 URL 提供单页应用程序的代码。如果您想要更干净的 URL,请使用此策略。

使用哈希策略的单页应用程序通常使用在哈希后添加感叹号的约定,以表明它们正在使用哈希作为路由机制,而不是用于链接到锚点。#! 字符串被称为 hashbang。

默认策略使用 hashbang。

典型用法 ​

通常,需要创建一些组件来映射路由:

javascript
var Home = {
  view: function () {
    return [m(Menu), m('h1', 'Home')];
  },
};

var Page1 = {
  view: function () {
    return [m(Menu), m('h1', 'Page 1')];
  },
};

在上面的示例中,有两个组件:Home 和 Page1。每个组件都包含一个菜单和一些文本。菜单本身被定义为一个组件,以避免重复:

javascript
var Menu = {
  view: function () {
    return m('nav', [
      m(m.route.Link, { href: '/' }, 'Home'),
      m(m.route.Link, { href: '/page1' }, 'Page 1'),
    ]);
  },
};

现在我们可以定义路由并将我们的组件映射到它们:

javascript
m.route(document.body, '/', {
  '/': Home,
  '/page1': Page1,
});

在这里,我们指定了两个路由:/ 和 /page1,当用户导航到每个 URL 时,它们会渲染各自的组件。

导航到不同的路由 ​

在上面的示例中,Menu 组件有两个 m.route.Link。这会创建一个元素(默认为 <a> 标签),并设置点击事件,使用户能够导航到另一个路由。它执行的是客户端路由,而不是远程导航。

您还可以通过 m.route.set(route) 以编程方式导航。例如,m.route.set("/page1")。

在路由之间导航时,路由器会自动处理前缀。换句话说,在链接 Mithril.js 路由时,请省略 hashbang #!(或您设置的 m.route.prefix 前缀),包括在 m.route.set 和 m.route.Link 中。

请注意,在组件之间导航时,整个子树会被替换。如果只想更新部分子树,可以使用带有 render 方法的路由解析器。

路由参数 ​

有时,我们希望在路由中包含可变的 ID 或其他数据,但又不想为每个可能的 ID 都定义一个单独的路由。为了实现这个目的,Mithril.js 提供了参数化路由功能:

javascript
var Edit = {
  view: function (vnode) {
    return [m(Menu), m('h1', 'Editing ' + vnode.attrs.id)]; // 正在编辑ID为:  + vnode.attrs.id +  的内容
  },
};
m.route(document.body, '/edit/1', {
  '/edit/:id': Edit,
});

在上面的示例中,我们定义了一个路由 /edit/:id。这将创建一个动态路由,该路由匹配任何以 /edit/ 开头并后跟一些数据的 URL(例如,/edit/1、edit/234 等)。然后,id 值将映射为组件的 vnode 的属性 (vnode.attrs.id)。

可以在路由中定义多个参数,例如,对于路由 /edit/:projectID/:userID,组件 vnode 的属性对象上会包含 projectID 和 userID 两个属性。

键参数 ​

当用户从一个参数化路由导航到另一个参数不同的相同路由时(例如,从 /page/1 切换到 /page/2,假设路由定义为 /page/:id),组件默认不会重新创建,因为这两个路由会解析到同一个组件,从而触发虚拟 DOM 的就地更新。这样做会导致触发 onupdate 钩子,而不是 oninit 或 oncreate 钩子。然而,开发者通常希望组件的重新创建与路由变化事件同步。

为此,可以将路由参数化与 键 结合使用,以实现方便的模式:

javascript
m.route(document.body, '/edit/1', {
  '/edit/:key': Edit,
});

这意味着为路由的根组件创建的 vnode 会包含一个名为 key 的路由参数对象。路由参数会成为 vnode 的 attrs 属性。因此,当从一个页面跳转到另一个页面时,key 的值会发生变化,从而导致组件重新创建(因为 key 告诉虚拟 DOM 引擎,新旧组件是不同的实体)。

您可以进一步扩展这个想法,以创建在重新加载时重新创建自身的组件:

m.route.set(m.route.get(), {key: Date.now()})

或者甚至可以使用 history state 功能来实现可重新加载的组件,而不会影响 URL:

m.route.set(m.route.get(), null, {state: {key: Date.now()}})

请注意,键参数仅适用于组件路由。如果使用路由解析器,则需要使用单元素键控片段,并传递 key: m.route.param("key") 来实现相同的效果。

可变路由 ​

也可以使用可变路由,即可变路由的参数可以包含带有斜杠的 URL 路径名:

javascript
m.route(document.body, '/edit/pictures/image.jpg', {
  '/edit/:file...': Edit,
});

处理 404 ​

对于同构/通用 JavaScript 应用,URL 参数和可变路由的组合非常适合用于显示自定义 404 错误页面。

当发生 404 Not Found 错误时,服务器会返回自定义的 404 页面给客户端。当 Mithril.js 加载后,由于无法识别该路由,它会将客户端重定向到默认路由。

javascript
m.route(document.body, '/', {
  '/': homeComponent,
  // [...]
  '/:404...': errorPageComponent,
});

历史记录状态 ​

可以充分利用底层的 history.pushState API 来提升用户的导航体验。例如,应用程序可以在用户离开页面时“记住”大型表单的状态,这样,当用户点击浏览器的后退按钮时,表单内容仍然存在,而不是显示一个空白表单。

例如,您可以创建一个像这样的表单:

javascript
var state = {
  term: '',
  search: function () {
    // save the state for this route
    // this is equivalent to `history.replaceState({term: state.term}, null, location.href)`
    m.route.set(m.route.get(), null, {
      replace: true,
      state: { term: state.term },
    });

    // navigate away
    location.href = 'https://google.com/?q=' + state.term;
  },
};

var Form = {
  oninit: function (vnode) {
    state.term = vnode.attrs.term || ''; // populated from the `history.state` property if the user presses the back button
  },
  view: function () {
    return m('form', [
      m("input[placeholder='Search']", {
        oninput: function (e) {
          state.term = e.target.value;
        },
        value: state.term,
      }),
      m('button', { onclick: state.search }, 'Search'),
    ]);
  },
};

m.route(document.body, '/', {
  '/': Form,
});

这样,如果用户搜索并按下后退按钮返回到应用程序,则输入仍将填充搜索词。此技术可以改善复杂表单和其他用户难以重现临时状态的应用程序的用户体验。

更改路由器前缀 ​

路由器前缀是 URL 的一部分,它决定了路由器使用的底层 策略。

javascript
// set to pathname strategy
m.route.prefix = ''; // 设置为路径名模式

// set to querystring strategy
m.route.prefix = '?'; // 设置为查询字符串模式

// set to hash without bang
m.route.prefix = '#'; // 设置为不带 bang 的哈希

// set to pathname strategy on a non-root URL
// e.g. if the app lives under `https://localhost/my-app` and something else
// lives under `https://localhost`
m.route.prefix = '/my-app'; // 在非根 URL 下设置为路径名模式

高级组件解析 ​

除了将组件直接关联到路由之外,你还可以指定一个 RouteResolver 对象。RouteResolver 对象包含一个 onmatch() 和/或一个 render() 方法。这两个方法都是可选的,但至少要存在一个。

javascript
m.route(document.body, '/', {
  '/': {
    onmatch: function (args, requestedPath, route) {
      return Home;
    },
    render: function (vnode) {
      return vnode; // 相当于 m(Home)
    },
  },
});

RouteResolver 在实现各种高级路由用例时非常有用。

包装布局组件 ​

通常,我们希望将所有或大部分路由组件包装在一个可重用的外壳(通常称为“布局”)中。为此,你需要首先创建一个包含通用标记的组件,用于包装各种不同的组件:

javascript
var Layout = {
  view: function (vnode) {
    return m('.layout', vnode.children);
  },
};

在上面的例子中,布局仅包含一个 <div class="layout">,用于容纳传递给组件的子元素。但在实际场景中,它可以根据需要变得非常复杂。

一种包装布局的方法是在路由映射中定义一个匿名组件:

javascript
// 示例 1
m.route(document.body, '/', {
  '/': {
    view: function () {
      return m(Layout, m(Home));
    },
  },
  '/form': {
    view: function () {
      return m(Layout, m(Form));
    },
  },
});

但是,请注意,由于顶层组件是匿名组件,因此从 / 路由跳转到 /form 路由(或反之)会导致该匿名组件被卸载,并从头开始重新创建 DOM。如果 Layout 组件定义了生命周期方法,则 oninit 和 oncreate 钩子会在每次路由更改时触发。具体是否适合取决于应用程序的需求。

如果你希望 Layout 组件通过差异化算法更新并保持状态,而不是重新创建,则应使用 RouteResolver 作为根对象:

javascript
// 示例 2
m.route(document.body, '/', {
  '/': {
    render: function () {
      return m(Layout, m(Home));
    },
  },
  '/form': {
    render: function () {
      return m(Layout, m(Form));
    },
  },
});

请注意,在这种情况下,如果 Layout 组件具有 oninit 和 oncreate 生命周期方法,它们将仅在第一次路由更改时触发(假设所有路由都使用相同的布局)。

为了更清楚地说明这两个示例之间的区别,示例 1 等效于以下代码:

javascript
// 在功能上等效于示例 1
var Anon1 = {
  view: function () {
    return m(Layout, m(Home));
  },
};
var Anon2 = {
  view: function () {
    return m(Layout, m(Form));
  },
};

m.route(document.body, '/', {
  '/': {
    render: function () {
      return m(Anon1);
    },
  },
  '/form': {
    render: function () {
      return m(Anon2);
    },
  },
});

由于 Anon1 和 Anon2 是不同的组件,因此它们的子树(包括 Layout 组件)会从头开始重新创建。直接使用组件而不通过 RouteResolver 时也会如此。

在示例 2 中,由于 Layout 是两个路由中的顶层组件,因此 Layout 组件的 DOM 会通过差异化算法进行更新(即,如果没有更改,则保持不变),只有从 Home 到 Form 的更改才会触发 DOM 中相应部分的重新创建。

重定向 ​

RouteResolver 的 onmatch 钩子可用于在路由中的顶层组件初始化之前执行一些逻辑。你可以使用 Mithril 的 m.route.set() 或原生 HTML 的 history API。当使用 history API 进行重定向时,onmatch 钩子必须返回一个永远不会 resolve 的 Promise,以阻止对匹配路由的解析。m.route.set() 会在内部取消匹配路由的解析,因此使用它时不需要这样做。

示例:身份验证 ​

下面的示例展示了如何实现一个登录验证,以防止用户在未登录的情况下访问 /secret 页面。

javascript
var isLoggedIn = false;

var Login = {
  view: function () {
    return m('form', [
      m(
        'button[type=button]',
        {
          onclick: function () {
            isLoggedIn = true;
            m.route.set('/secret');
          },
        },
        'Login'
      ),
    ]);
  },
};

m.route(document.body, '/secret', {
  '/secret': {
    onmatch: function () {
      if (!isLoggedIn) m.route.set('/login');
      else return Home;
    },
  },
  '/login': Login,
});

当应用程序加载时,会调用 onmatch,并且由于 isLoggedIn 为 false,应用程序会重定向到 /login。一旦用户点击登录按钮,isLoggedIn 就会被设置为 true,并且应用程序会重定向到 /secret。onmatch 钩子会再次运行,并且由于此时 isLoggedIn 为 true,应用程序会渲染 Home 组件。

为了简化,在上面的示例中,用户的登录状态保存在一个全局变量中,并且当用户点击登录按钮时,该标志会被切换。在实际应用中,用户需要提供正确的登录凭据,点击登录按钮会触发向服务器发送的身份验证请求:

javascript
var Auth = {
  username: '',
  password: '',

  setUsername: function (value) {
    Auth.username = value;
  },
  setPassword: function (value) {
    Auth.password = value;
  },
  login: function () {
    m.request({
      url: '/api/v1/auth',
      params: { username: Auth.username, password: Auth.password },
    }).then(function (data) {
      localStorage.setItem('auth-token', data.token);
      m.route.set('/secret');
    });
  },
};

var Login = {
  view: function () {
    return m('form', [
      m('input[type=text]', {
        oninput: function (e) {
          Auth.setUsername(e.target.value);
        },
        value: Auth.username,
      }),
      m('input[type=password]', {
        oninput: function (e) {
          Auth.setPassword(e.target.value);
        },
        value: Auth.password,
      }),
      m('button[type=button]', { onclick: Auth.login }, 'Login'),
    ]);
  },
};

m.route(document.body, '/secret', {
  '/secret': {
    onmatch: function () {
      if (!localStorage.getItem('auth-token')) m.route.set('/login');
      else return Home;
    },
  },
  '/login': Login,
});

预加载数据 ​

通常,组件会在初始化时加载数据。以这种方式加载数据会导致组件渲染两次。第一次渲染发生在路由跳转时,第二次渲染在请求完成后触发。请注意,loadUsers() 返回一个 Promise,但是目前 oninit 返回的任何 Promise 都会被忽略。第二次渲染来自 m.request 的 background 选项。

javascript
var state = {
  users: [],
  loadUsers: function () {
    return m.request('/api/v1/users').then(function (users) {
      state.users = users;
    });
  },
};

m.route(document.body, '/user/list', {
  '/user/list': {
    oninit: state.loadUsers,
    view: function () {
      return state.users.length > 0
        ? state.users.map(function (user) {
            return m('div', user.id);
          })
        : 'loading';
    },
  },
});

在上面的示例中,在第一次渲染时,UI 显示 "loading",因为在请求完成之前 state.users 是一个空数组。然后,一旦数据可用,UI 就会重绘并显示用户 ID 列表。

RouteResolvers 可以用作一种机制,在渲染组件之前预加载数据,以避免 UI 闪烁,从而避免显示加载指示器:

javascript
var state = {
  users: [],
  loadUsers: function () {
    return m.request('/api/v1/users').then(function (users) {
      state.users = users;
    });
  },
};

m.route(document.body, '/user/list', {
  '/user/list': {
    onmatch: state.loadUsers,
    render: function () {
      return state.users.map(function (user) {
        return m('div', user.id);
      });
    },
  },
});

在上面的例子中,render 只会在请求完成后运行,因此三元运算符变得多余。

代码分割 ​

在大型应用中,通常需要按需加载各路由的代码,而不是预先下载。以这种方式划分代码库被称为代码分割或懒加载。在 Mithril.js 中,可以通过从 onmatch 钩子返回一个 Promise 来实现。

在最基本的形式下,可以执行以下操作:

javascript
// Home.js
module.export = {
  view: function () {
    return [m(Menu), m('h1', 'Home')];
  },
};
javascript
// index.js
function load(file) {
  return m.request({
    method: 'GET',
    url: file,
    extract: function (xhr) {
      return new Function(
        'var module = {};' + xhr.responseText + ';return module.exports;'
      );
    },
  });
}

m.route(document.body, '/', {
  '/': {
    onmatch: function () {
      return load('Home.js');
    },
  },
});

然而,实际上,为了使其在生产环境中正常工作,有必要将 Home.js 模块的所有依赖项打包到最终由服务器提供的文件中。

幸运的是,有很多工具可以简化打包模块以进行延迟加载的任务。这是一个使用 原生动态 import(...) 的示例,许多打包器都支持它:

javascript
m.route(document.body, '/', {
  '/': {
    onmatch: function () {
      return import('./Home.js');
    },
  },
});

类型化的路由 ​

在某些高级路由场景下,你可能希望对路由参数进行更严格的约束,而不仅仅是路径本身,例如只允许匹配数字 ID。你可以通过从路由返回 m.route.SKIP 来轻松实现这一点。

javascript
m.route(document.body, '/', {
  '/view/:id': {
    onmatch: function (args) {
      if (!/^\d+$/.test(args.id)) return m.route.SKIP;
      return ItemView;
    },
  },
  '/view/:name': UserView,
});

隐藏路由 ​

在少数情况下,你可能希望对某些用户隐藏某些路由,而不是对所有用户都可见。例如,你可能希望禁止用户查看特定用户的信息,并且你宁愿假装该用户不存在并重定向到 404 页面,而不是显示权限错误。在这种情况下,你可以使用 m.route.SKIP 来模拟该路由不存在的情况。

javascript
m.route(document.body, '/', {
  '/user/:id': {
    onmatch: function (args) {
      return Model.checkViewable(args.id).then(function (viewable) {
        return viewable ? UserView : m.route.SKIP;
      });
    },
  },
  '/:404...': PageNotFound,
});

路由取消/阻止 ​

RouteResolver 的 onmatch 方法可以通过返回一个永远不会 resolve 的 Promise 来阻止路由解析。这可以用于检测并取消重复的路由解析尝试。

javascript
m.route(document.body, '/', {
  '/': {
    onmatch: function (args, requestedPath) {
      if (m.route.get() === requestedPath) return new Promise(function () {});
    },
  },
});

第三方集成 ​

在某些情况下,你可能需要与其他框架(如 React)进行集成。以下是如何操作:

  • 像往常一样使用 m.route 定义所有路由,但请确保只调用 一次。不支持定义多个路由入口点。
  • 当你需要移除路由时,使用 m.mount(root, null),其中 root 是你之前调用 m.route(root, ...) 时使用的根元素。m.route 内部使用 m.mount 来完成路由的挂载,所以这并不是什么黑魔法。

这是一个使用 React 的示例:

jsx
class Child extends React.Component {
  constructor(props) {
    super(props);
    this.root = React.createRef();
  }

  componentDidMount() {
    m.route(this.root, '/', {
      // ...
    });
  }

  componentDidUnmount() {
    m.mount(this.root, null);
  }

  render() {
    return <div ref={this.root} />;
  }
}

下面是与 Vue 对应的示例:

html
<div ref="root"></div>
javascript
Vue.component('my-child', {
  template: `<div ref="root"></div>`,
  mounted: function () {
    m.route(this.$refs.root, '/', {
      // ...
    });
  },
  destroyed: function () {
    m.mount(this.$refs.root, null);
  },
});
Pager
上一页mount(root, component)
下一页request(options)

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors

https://mithril.js.org/route.html

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors