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

入门

安装

简单应用

资源

JSX

在旧版浏览器上使用 ES6+

动画

测试

示例

第三方库集成

路径处理

关键概念

虚拟 DOM

组件

生命周期方法

键(Key)

自动重绘系统

杂项

框架对比

从 v1.x 迁移

从 v0.2.x 迁移

API

页面导航

从 v0.2.x 迁移 ​

v1.x 和 v2.x 在很大程度上与 v0.2.x 的 API 兼容,但仍然存在一些重大更改。迁移到 v2.x 的过程与 v0.2.x 类似,因此以下说明主要适用于两者。

如果您正在迁移,请考虑使用 mithril-codemods 工具来帮助自动化最简单的迁移过程。

m.prop 已移除 ​

在 v2.x 中,m.prop() 被改造为一个功能更强大的流微型库,但它已不再是核心部分。您可以在文档中阅读有关如何使用可选的 Streams 模块。

v0.2.x ​

javascript
var m = require('mithril');

var num = m.prop(1);

v2.x ​

javascript
var m = require('mithril');
var prop = require('mithril/stream');

var num = prop(1);
var doubled = num.map(function (n) {
  return n * 2;
});

m.component 已移除 ​

在 v0.2.x 中,可以使用 m(Component) 或 m.component(Component) 创建组件。v2.x 仅支持 m(Component)。

v0.2.x ​

javascript
// 这些是等效的
m.component(Component);
m(Component);

v2.x ​

javascript
m(Component);

m.withAttr 已移除 ​

在 v0.2.x 中,事件监听器可以使用 oninput: m.withAttr("value", func) 等。在 v2.x 中,可以直接从事件的目标对象读取属性。它曾经与 m.prop 配合良好,但由于 m.prop 被移除,转而使用核心外的解决方案,并且 v1.x 中没有出现类似的大规模、符合习惯的流用法,因此 m.withAttr 失去了大部分用处。

v0.2.x ​

javascript
var value = m.prop('');

// 在您的视图中
m('input[type=text]', {
  value: value(),
  oninput: m.withAttr('value', value),
});

v2.x ​

javascript
var value = '';

// 在您的视图中
m('input[type=text]', {
  value: value,
  oninput: function (ev) {
    value = ev.target.value;
  },
});

m.version 已移除 ​

它的用途通常不大,您可以随时自行添加回来。建议使用功能检测来了解可用的功能,并且 v2.x API 旨在更好地实现这一点。

config 函数 ​

在 v0.2.x 中,Mithril.js 提供了一个名为 config 的生命周期方法。v2.x 提供了对 vnode 生命周期更精细的控制。

v0.2.x ​

javascript
m('div', {
  config: function (element, isInitialized) {
    // 每次重绘时运行
    // isInitialized 是一个布尔值,表示节点是否已添加到 DOM
  },
});

v2.x ​

有关这些新方法的详细文档,请参见 lifecycle-methods.md。

javascript
m('div', {
  // 在 DOM 节点创建前调用
  oninit: function (vnode) {
    /*...*/
  },
  // 在创建 DOM 节点之后调用
  oncreate: function (vnode) {
    /*...*/
  },
  // 在更新节点前调用,返回 false 可取消更新
  onbeforeupdate: function (vnode, old) {
    /*...*/
  },
  // 在更新节点之后调用
  onupdate: function (vnode) {
    /*...*/
  },
  // 在删除节点前调用,返回一个 Promise,表示节点准备好被删除
  onbeforeremove: function (vnode) {
    /*...*/
  },
  // 在删除节点之前调用,但在 onbeforeremove 调用 done() 之后执行
  onremove: function (vnode) {
    /*...*/
  },
});

如果可用,则可以在 vnode.dom 访问 vnode 的 DOM 元素。

重绘行为的更改 ​

Mithril.js 的渲染引擎仍然基于半自动的全局重绘运行,但某些 API 和行为有所不同:

不再有重绘锁 ​

在 v0.2.x 中,Mithril.js 允许“重绘锁”,该锁暂时阻止了阻塞绘制逻辑:默认情况下,m.request 会在执行时锁定绘制循环,并在所有挂起的请求都已解决时解锁 - 可以使用 m.startComputation() 和 m.endComputation() 手动调用相同的行为。后者 API 和相关的行为已在 v2.x 中删除,没有替代项。重绘锁定可能导致 UI 错误:应用程序的一部分不应阻止其他部分的视图更新。

从事件处理程序取消重绘 ​

m.mount() 和 m.route() 仍然会在 DOM 事件处理程序运行后自动重绘。现在,可以通过将事件对象的 redraw 属性设置为 false,来取消事件处理程序中的重绘。

v0.2.x ​

javascript
m('div', {
  onclick: function (e) {
    m.redraw.strategy('none');
  },
});

v2.x ​

javascript
m('div', {
  onclick: function (e) {
    e.redraw = false;
  },
});

同步重绘已更改 ​

在 v0.2.x 中,可以通过传递真值给 m.redraw() 强制立即重绘。在 v2.x 中,此功能被拆分为两个不同的方法,以提高清晰度。

v0.2.x ​

javascript
m.redraw(true); // 立即且同步地重绘

v2.x ​

javascript
m.redraw(); // 在下一个 requestAnimationFrame 时钟周期上安排重绘
m.redraw.sync(); // 立即调用重绘并等待完成

m.startComputation/m.endComputation 已移除 ​

这些方法被视为反模式,并且有许多有问题的边缘情况,因此它们已在 v2.x 中删除,没有替代项。

组件 controller 函数 ​

在 v2.x 中,组件不再有 controller 属性,改用 oninit。

v0.2.x ​

javascript
m.mount(document.body, {
  controller: function () {
    var ctrl = this;

    ctrl.fooga = 1;
  },

  view: function (ctrl) {
    return m('p', ctrl.fooga);
  },
});

v2.x ​

javascript
m.mount(document.body, {
  oninit: function (vnode) {
    vnode.state.fooga = 1;
  },

  view: function (vnode) {
    return m('p', vnode.state.fooga);
  },
});

// 或者

m.mount(document.body, {
  // 默认情况下,this 绑定到 vnode.state
  oninit: function (vnode) {
    this.fooga = 1;
  },

  view: function (vnode) {
    return m('p', this.fooga);
  },
});

组件参数 ​

v2.x 中组件参数必须为对象,像 String/Number/Boolean 这样的简单值会被视为文本子节点。在组件中通过 vnode.attrs 访问参数。

v0.2.x ​

javascript
var Component = {
  controller: function (options) {
    // options.fooga === 1
  },

  view: function (ctrl, options) {
    // options.fooga === 1
  },
};

m('div', m.component(Component, { fooga: 1 }));

v2.x ​

javascript
var Component = {
  oninit: function (vnode) {
    // vnode.attrs.fooga === 1
  },

  view: function (vnode) {
    // vnode.attrs.fooga === 1
  },
};

m('div', m(Component, { fooga: 1 }));

组件 vnode 子节点 ​

在 v0.2.x 中,组件 vnode 的子节点未被规范化,只是作为额外的参数传递,并且它们也没有被扁平化。(从内部实现来看,它只是返回一个部分应用的组件,并基于该组件进行差异比较。)在 v2.x 中,组件 vnode 子节点通过 vnode.children 作为已解析的子节点数组传递,但与 v0.2.x 一样,各个子节点本身未被规范化,子节点数组也没有被扁平化。

v0.2.x ​

javascript
var Component = {
  controller: function (value, renderProp) {
    // value === "value"
    // typeof renderProp === "function"
  },

  view: function (ctrl, value, renderProp) {
    // value === "value"
    // typeof renderProp === "function"
  },
};

m(
  'div',
  m.component(Component, 'value', function (key) {
    return 'child';
  })
);

v2.x ​

javascript
var Component = {
  oninit: function (vnode) {
    // vnode.children[0] === "value"
    // typeof vnode.children[1] === "function"
  },

  view: function (vnode) {
    // vnode.children[0] === "value"
    // typeof vnode.children[1] === "function"
  },
};

m(
  'div',
  m(Component, 'value', function (key) {
    return 'child';
  })
);

DOM vnode 子节点 ​

在 v0.2.x 中,DOM 节点的子节点以字面量形式表示,除了当只有一个数组类型的子节点时会直接使用该数组,没有进行额外的规范化。它返回的结构更像这样,字符串以字面形式表示。

javascript
m("div", "value", ["nested"])

// 变为:
{
	tag: "div",
	attrs: {},
	children: [
		"value",
		["nested"],
	]
}

在 v2.x 中,DOM vnode 的子节点被规范化为一致的对象结构。

javascript
m("div", "value", ["nested"])

// 大致变为:
{
	tag: "div",
	attrs: null,
	children: [
		{tag: "#", children: "value"},
		{tag: "[", children: [
			{tag: "#", children: "nested"},
		]},
	]
}

如果 DOM vnode 仅有一个文本子节点,则将 text 设置为该值。

javascript
m("div", "value")

// 大致变为:
{
	tag: "div",
	attrs: null,
	text: "",
	children: undefined,
}

有关 v2.x vnode 结构及其规范化的详细信息,请参见 vnode 文档。

为了简洁起见,此处省略了大多数 v2.x vnode 属性。

键 ​

在 v0.2.x 中,可以自由混合键控和非键控 vnode。

在 v2.x 中,片段和元素的子节点列表必须全部键控或全部非键控。为了进行此项检查,空位也被认为是非键控的 - 现在不会再忽略它们。

如需解决此问题,请使用包含单个 vnode 的片段。

view() 参数 ​

在 v0.2.x 中,视图函数传递 controller 实例引用及可选的组件选项。在 v2.x 中,仅传递 vnode,与 controller 函数完全相同。

v0.2.x ​

javascript
m.mount(document.body, {
  controller: function () {},

  view: function (ctrl, options) {
    // ...
  },
});

v2.x ​

javascript
m.mount(document.body, {
  oninit: function (vnode) {
    // ...
  },

  view: function (vnode) {
    // 使用 vnode.state 代替 ctrl
    // 使用 vnode.attrs 代替 options
  },
});

将组件传递给 m() ​

在 v0.2.x 中,可以将组件作为 m() 的第二个参数传递,无需包装。为了保持 v2.x 中的一致性,它们必须始终用 m() 函数调用进行包装。

v0.2.x ​

javascript
m('div', Component);

v2.x ​

javascript
m('div', m(Component));

将 vnode 传递给 m.mount() 和 m.route() ​

在 v0.2.x 中,m.mount(element, component) 允许将 vnode 作为第二个参数,而不是 组件(即使这没有被记录在文档中)。同样,m.route(element, defaultRoute, routes) 接受 vnode 作为 routes 对象中的值。

在 v2.x 中,两种情况均需组件。

v0.2.x ​

javascript
m.mount(element, m('i', 'hello'));
m.mount(element, m(Component, attrs));

m.route(element, '/', {
  '/': m('b', 'bye'),
});

v2.x ​

javascript
m.mount(element, {
  view: function () {
    return m('i', 'hello');
  },
});
m.mount(element, {
  view: function () {
    return m(Component, attrs);
  },
});

m.route(element, '/', {
  '/': {
    view: function () {
      return m('b', 'bye');
    },
  },
});

m.route.mode ​

在 v0.2.x 中,可以通过将字符串 "pathname"、"hash" 或 "search" 分配给 m.route.mode 来设置路由模式。在 v.1.x 中,已被 m.route.prefix = prefix 替换,其中 prefix 可以是任何前缀。如果前缀以 # 开头,则使用"hash"模式,? 以“search”模式工作,任何其他字符(或空字符串)以“pathname”模式工作。它还支持上述组合,例如 m.route.prefix = "/path/#!" 或 ?#。

默认值也改为使用 #!(hashbang)前缀,而不仅仅是 #。因此,如果您使用的是默认行为并且想要保留现有的 URL,请在初始化路由之前设置 m.route.prefix = "#"。

v0.2.x ​

javascript
m.route.mode = 'hash';
m.route.mode = 'pathname';
m.route.mode = 'search';

v2.x ​

javascript
// 直接等效项
m.route.prefix = '#';
m.route.prefix = '';
m.route.prefix = '?';

m.route() 和锚标记 ​

现在处理可路由链接需要使用特殊的内置组件,而不是属性。在 <button> 等上使用时,可通过 selector: "button" 属性指定标记名称。

v0.2.x ​

javascript
// 单击此链接时,将加载“/path”路由而不是导航
m('a', {
  href: '/path',
  config: m.route,
});

v2.x ​

javascript
// 单击此链接时,将加载“/path”路由而不是导航
m(m.route.Link, {
  href: '/path',
});

路径模板 ​

在 v1.x 中,有三种路径模板语法,尽管相似,但设计和实现各有不同。它的定义方式相当随意,并且参数通常没有进行转义。现在,若为 :key,则所有内容编码;若为 :key...,则为原始。如果出现意外的编码情况,请使用 :path...。就这么简单。

具体来说,以下是它如何影响每种方法:

m.request URL ​

v2.x 中的路径组件在插值时会自动转义,并且它们从 params 中读取它们的值。在 v0.2.x 中,m.request({url: "/user/:name/photos/:id", data: {name: "a/b", id: "c/d"}}) 将发送其请求,并将 URL 设置为 /user/a%2Fb/photos/c/d。在 v2.x 中,相应的 m.request({url: "/user/:name/photos/:id", params: {name: "a/b", id: "c/d"}}) 将发送其请求到 /user/a%2Fb/photos/c%2Fd。如果您确实_想要_插入一个未转义的键,请改用 :key...。

v2.x 不会对内联查询字符串执行插值,例如 /api/search?q=:query。请通过 params 传递这些,并使用适当的键名,而无需在查询字符串中指定它。

需要注意的是,这一变化同样适用于 m.jsonp。从 m.request + dataType: "jsonp" 迁移到 m.jsonp 时,您还需要注意这一点。

m.route(route, params, shouldReplaceHistoryEntry) 路径 ​

这些路径现在支持插值,其工作方式与 m.request 相同。

m.route 路由模式 ​

形式为 :key... 的路径键在 v1.x 中返回其 URL 解码,但在 v2.x 中返回原始 URL。

以前,像 :key.md 这样的东西被错误地接受,并且生成的参数的值设置为 keymd: "..."。现在情况并非如此 - .md 现在是模式的一部分,而不是名称。

读取/写入当前路由 ​

在 v0.2.x 中,与当前路由的所有交互都通过 m.route() 进行。在 v2.x 中,这一功能被拆分为两个函数。

v0.2.x ​

javascript
// 获取当前路由
m.route();

// 设置新路由
m.route('/other/route');

v2.x ​

javascript
// 获取当前路由
m.route.get();

// 设置新路由
m.route.set('/other/route');

访问路由参数 ​

在 v0.2.x 中,路由参数通过 m.route.param() 完全读取。此 API 在 v2.x 中仍可用,且路由参数作为 vnode 的 attrs 属性传递。

v0.2.x ​

javascript
m.route(document.body, '/booga', {
  '/:attr': {
    controller: function () {
      m.route.param('attr'); // "booga"
    },
    view: function () {
      m.route.param('attr'); // "booga"
    },
  },
});

v2.x ​

javascript
m.route(document.body, '/booga', {
  '/:attr': {
    oninit: function (vnode) {
      vnode.attrs.attr; // "booga"
      m.route.param('attr'); // "booga"
    },
    view: function (vnode) {
      vnode.attrs.attr; // "booga"
      m.route.param('attr'); // "booga"
    },
  },
});

构建/解析查询字符串 ​

v0.2.x 使用 m.route 上的方法 m.route.buildQueryString() 和 m.route.parseQueryString()。在 v2.x 中,这些功能被提取出来并移至根 m 对象。

v0.2.x ​

javascript
var qs = m.route.buildQueryString({ a: 1 });

var obj = m.route.parseQueryString('a=1');

v2.x ​

javascript
var qs = m.buildQueryString({ a: 1 });

var obj = m.parseQueryString('a=1');

此外,在 v2.x 中,{key: undefined} 被 m.buildQueryString 和相关方法(如 m.request)序列化为 key=undefined。在 v0.2.x 中,省略了键,并且这延续到了 m.request。如依赖此行为,请修改代码以从对象中省略键。如果您无法轻松做到这一点并且需要保留 v0.2.x 行为,则可能值得使用一个简单的实用程序从对象中删除所有值为 undefined 的键。

javascript
// 当需要从对象中移除值为 `undefined` 的属性时调用此方法
function omitUndefineds(object) {
  var result = {};

  for (var key in object) {
    if ({}.hasOwnProperty.call(object, key)) {
      var value = object[key];
      if (Array.isArray(value)) {
        result[key] = value.map(omitUndefineds);
      } else if (value != null && typeof value === 'object') {
        result[key] = omitUndefineds(value);
      } else if (value !== undefined) {
        result[key] = value;
      }
    }
  }

  return result;
}

阻止卸载 ​

无法再通过 onunload 的 e.preventDefault() 阻止卸载。应在满足条件时显式调用 m.route.set。

v0.2.x ​

javascript
var Component = {
  controller: function () {
    this.onunload = function (e) {
      if (condition) e.preventDefault();
    };
  },
  view: function () {
    return m('a[href=/]', { config: m.route });
  },
};

v2.x ​

javascript
var Component = {
  view: function () {
    return m('a', {
      onclick: function () {
        if (!condition) m.route.set('/');
      },
    });
  },
};

在组件删除时运行代码 ​

组件在被移除时不再调用 this.onunload。现在改用标准化的生命周期钩子 onremove。

v0.2.x ​

javascript
var Component = {
  controller: function () {
    this.onunload = function (e) {
      // ...
    };
  },
  view: function () {
    // ...
  },
};

v2.x ​

javascript
var Component = {
	onremove: function() {
		// ...
	}
	view: function() {
		// ...
	}
}

m.request ​

m.request 返回的 Promise 不再是 m.prop 的 getter/setter。此外,不再支持 initialValue、unwrapSuccess 和 unwrapError 选项。

此外,请求不再具有 m.startComputation/m.endComputation 的语义。相反,当请求 Promise 链完成时,总会触发重绘(除非设置了 background: true)。

data 参数现在拆分为 params(插入到 URL 中并附加到请求的查询参数)和 body(作为底层 XHR 请求的正文发送)。

在 v0.2.x 中,使用 dataType: "jsonp" 启动 JSONP 请求。在 v2.x 中,使用 m.jsonp,其 API 与 m.request 基本相同,但不包含与 XHR 相关的部分。

v0.2.x ​

javascript
var data = m.request({
  method: 'GET',
  url: 'https://api.github.com/',
  initialValue: [],
});

setTimeout(function () {
  console.log(data());
}, 1000);

m.request({
  method: 'POST',
  url: 'https://api.github.com/',
  data: someJson,
});

v2.x ​

javascript
var data = [];
m.request({
  method: 'GET',
  url: 'https://api.github.com/',
}).then(function (responseBody) {
  data = responseBody;
});

setTimeout(function () {
  console.log(data); // 注意:不是 getter-setter
}, 1000);

m.request({
  method: 'POST',
  url: 'https://api.github.com/',
  body: someJson,
});

// 或者

var data = [];
m.request('https://api.github.com/').then(function (responseBody) {
  data = responseBody;
});

setTimeout(function () {
  console.log(data); // 注意:不是 getter-setter
}, 1000);

m.request('https://api.github.com/', {
  method: 'POST',
  body: someJson,
});

此外,如果将 extract 选项传递给 m.request,则提供的函数的返回值将直接用于解析请求 Promise,并且 deserialize 回调将被忽略。

m.request 标头 ​

在 v0.2.x 中,Mithril.js 默认情况下未在请求上设置任何标头。现在,它最多设置 2 个标头:

  • 对于具有 != null 的 JSON 正文的请求,Content-Type: application/json; charset=utf-8
  • 对于期望 JSON 响应的请求,Accept: application/json, text/*

由于指定的内容类型,这两个标头中的第一个标头 Content-Type 将触发 CORS 预检,因为它不是 CORS 安全列出的请求标头,这可能会根据服务器上 CORS 的配置方式引入新的错误。如果您遇到此问题,您可能需要通过传递 headers: {"Content-Type": "text/plain"} 来覆盖有问题的标头。(Accept 标头不会触发任何内容,因此您无需覆盖它。)

Fetch 规范允许避免 CORS 预检检查的唯一内容类型是 application/x-www-form-urlencoded、multipart/form-data 和 text/plain。它不允许任何其他内容,并且它有意禁止 JSON。

m.deferred 已移除 ​

v0.2.x 使用了自己的自定义异步约定对象,该对象公开为 m.deferred,并用作 m.request 的基础。v2.x 改为使用 Promise,并在不支持的环境中提供了 polyfill。在原本需要使用 m.deferred 的场景下,您应该改为使用 Promise。

v0.2.x ​

javascript
var greetAsync = function () {
  var deferred = m.deferred();
  setTimeout(function () {
    deferred.resolve('hello');
  }, 1000);
  return deferred.promise;
};

greetAsync()
  .then(function (value) {
    return value + ' world';
  })
  .then(function (value) {
    console.log(value);
  }); //1 秒后记录“hello world”

v2.x ​

javascript
var greetAsync = function () {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve('hello');
    }, 1000);
  });
};

greetAsync()
  .then(function (value) {
    return value + ' world';
  })
  .then(function (value) {
    console.log(value);
  }); //1 秒后记录“hello world”

m.sync 已移除 ​

由于 v2.x 使用符合标准的 Promise,因此 m.sync 是多余的。请改用 Promise.all。

v0.2.x ​

javascript
m.sync([
  m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
  m.request({
    method: 'GET',
    url: 'https://api.github.com/users/dead-claudia',
  }),
]).then(function (users) {
  console.log('Contributors:', users[0].name, 'and', users[1].name);
});

v2.x ​

javascript
Promise.all([
  m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
  m.request({
    method: 'GET',
    url: 'https://api.github.com/users/dead-claudia',
  }),
]).then(function (users) {
  console.log('Contributors:', users[0].name, 'and', users[1].name);
});

需要 xlink 命名空间 ​

现在完全支持命名空间解析,并且带命名空间的属性应显式声明其命名空间。

v0.2.x ​

javascript
m(
  'svg',
  // `href` 属性会自动命名空间
  m("image[href='image.gif']")
);

v2.x ​

javascript
m(
  'svg',
  // 用户在 `href` 属性上指定的命名空间
  m("image[xlink:href='image.gif']")
);

视图中的嵌套数组 ​

数组现在代表 片段,这在 v2.x 虚拟 DOM 中具有结构意义。在 v0.2.x 中,嵌套数组会被扁平化成一个连续的虚拟节点列表用于比对,而 v2.x 则保留了数组结构 - 任何给定数组的子节点都不会被认为是相邻数组的同级节点。

vnode 相等性检查 ​

如果一个 vnode 与它在上次绘制中占据相同位置的 vnode 严格相等,那么 v2.x 将跳过树的该部分,而不会检查突变或触发子树中的任何生命周期方法。组件文档包含 有关此问题的更多详细信息。

Pager
上一页从 v1.x 迁移
下一页API

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors

https://mithril.js.org/migration-v02x.html

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors