生命周期方法
用法
组件 和 虚拟 DOM 节点 可以拥有生命周期方法,也称为 钩子(hooks),它们在 DOM 元素生命周期的各个阶段被调用。
// 组件中的示例钩子
var ComponentWithHook = {
oninit: function (vnode) {
console.log('initialize component'); // 组件初始化
},
view: function () {
return 'hello';
},
};
// vnode 中的示例钩子
function initializeVnode() {
console.log('initialize vnode'); // vnode 初始化
}
m(ComponentWithHook, { oninit: initializeVnode });
所有生命周期方法都接收 vnode 作为它们的第一个参数,并且它们的 this
关键字绑定到 vnode.state
。
生命周期方法仅作为 m.render()
调用的副作用被调用。如果 DOM 在 Mithril 之外被修改,它们不会被调用。
DOM 元素生命周期
一个 DOM 元素通常被创建并附加到文档中。然后,当 UI 事件被触发并且数据被更改时,它可能会更新属性或子节点;并且该元素可能会从文档中移除。
元素被移除后,可能会临时保存在内存池中。被缓存到内存池中的元素可能会在后续的更新中被重用(这个过程称为 DOM 回收(DOM recycling))。回收元素可以避免重新创建最近存在的元素副本,从而节省性能开销。
oninit
oninit(vnode)
钩子在 vnode 被虚拟 DOM 引擎处理之前被调用。oninit
保证在其 DOM 元素附加到文档之前被调用,并且保证在父 vnode 的子节点之前执行,但是它不保证祖先或后代 DOM 元素是否存在。你不应该在 oninit
方法中访问 vnode.dom
。
当一个元素被更新时,这个钩子不会被调用,但是如果一个元素被回收,它会被调用。
与其他钩子一样,oninit
回调中的 this
关键字指向 vnode.state
。
oninit
钩子对于根据通过 vnode.attrs
或 vnode.children
传递的参数初始化组件状态非常有用。
function ComponentWithState() {
var initialData;
return {
oninit: function (vnode) {
initialData = vnode.attrs.data;
},
view: function (vnode) {
return [
// 显示来自初始化时间的数据:
m('div', 'Initial: ' + initialData),
// 显示当前数据:
m('div', 'Current: ' + vnode.attrs.data),
];
},
};
}
m(ComponentWithState, { data: 'Hello' });
你不应该在此方法中同步修改模型数据。由于 oninit
不保证其他元素的状态,因此,通过此方法创建的模型更改可能不会立即反映在 UI 的所有部分,直到下一个渲染周期。
oncreate
oncreate(vnode)
钩子在一个 DOM 元素被创建并附加到文档之后被调用。oncreate
保证在渲染周期结束时运行,因此从此方法读取布局值(例如 vnode.dom.offsetHeight
和 vnode.dom.getBoundingClientRect()
)是安全的。
当一个元素被更新时,这个钩子不会被调用。
与其他钩子一样,oncreate
回调中的 this
关键字指向 vnode.state
。带有 oncreate
钩子的 vnode 对应的 DOM 元素不会被回收。
oncreate
钩子对于读取可能触发重绘的布局值、启动动画以及初始化需要引用 DOM 元素的第三方库非常有用。
var HeightReporter = {
oncreate: function (vnode) {
console.log('Initialized with height of: ', vnode.dom.offsetHeight); // 使用高度初始化:
},
view: function () {},
};
m(HeightReporter, { data: 'Hello' });
你不应该在此方法中同步修改模型数据。由于 oncreate
在渲染周期结束时运行,因此从这个方法创建的模型更改将不会反映在 UI 中,直到下一个渲染周期。
onupdate
onupdate(vnode)
钩子在一个 DOM 元素被更新时被调用,并且仍附加在文档中。onupdate
保证在渲染周期结束时运行,因此从此方法读取布局值(例如 vnode.dom.offsetHeight
和 vnode.dom.getBoundingClientRect()
)是安全的。
只有当元素在之前的渲染周期中存在时,此钩子才会被调用。当一个元素被创建或被回收时,它不会被调用。
带有 onupdate
钩子的 vnode 对应的 DOM 元素不会被回收。
onupdate
钩子对于读取可能触发重绘的布局值,以及在模型数据更改后动态更新第三方库中影响 UI 的状态非常有用。
function RedrawReporter() {
var count = 0;
return {
onupdate: function () {
console.log('Redraws so far: ', ++count); // 到目前为止的重绘次数:
},
view: function () {},
};
}
m(RedrawReporter, { data: 'Hello' });
onbeforeremove
onbeforeremove(vnode)
钩子在 DOM 元素从文档中分离之前被调用。如果返回一个 Promise,Mithril.js 仅在 promise 完成后才分离 DOM 元素。
这个钩子仅在失去其 parentNode
的 DOM 元素上被调用,而不会在其子元素中被调用。
与其他钩子一样,onbeforeremove
回调中的 this
关键字指向 vnode.state
。拥有 onbeforeremove
钩子的 vnode 对应的 DOM 元素不会被回收。
var Fader = {
onbeforeremove: function (vnode) {
vnode.dom.classList.add('fade-out');
return new Promise(function (resolve) {
setTimeout(resolve, 1000);
});
},
view: function () {
return m('div', 'Bye');
},
};
onremove
onremove(vnode)
钩子在 DOM 元素从文档中移除之后调用。如果也定义了一个 onbeforeremove
钩子,onremove
钩子在 onbeforeremove
返回的 promise 完成后运行。
这个钩子在从文档中移除的任何元素上被调用,无论它是直接从其父元素分离,还是作为另一个被分离的元素的子元素。
与其他钩子一样,onremove
回调中的 this
关键字指向 vnode.state
。拥有 onremove
钩子的 vnode 对应的 DOM 元素不会被回收。
onremove
钩子对于运行清理任务非常有用。
function Timer() {
var timeout = setTimeout(function () {
console.log('timed out'); // 超时
}, 1000);
return {
onremove: function () {
clearTimeout(timeout);
},
view: function () {},
};
}
onbeforeupdate
onbeforeupdate(vnode, old)
钩子在一个 vnode 更新前,在进行 diff 之前被调用。如果定义了此函数并返回 false,Mithril.js 将阻止对 vnode 及其子节点进行 diff 操作。
除非子树被封装在一个组件中,否则这个钩子本身不会阻止虚拟 DOM 子树的创建。
与其他钩子一样,onbeforeupdate
回调中的 this
关键字指向 vnode.state
。
当 DOM 树过大时,这个钩子对于减少更新延迟非常有用。
避免反模式
虽然 Mithril.js 很灵活,但一些代码模式是不鼓励的:
避免过早优化
你应只在万不得已时使用 onbeforeupdate
跳过 diff。除非你有一个明显的性能问题,否则避免使用它。
通常,可以通过 onbeforeupdate
解决的性能问题可以归结为大型项目数组。在这种情况下,通常“大”意味着任何包含大量节点的数组,无论是在广泛的分布中(典型的5000行表格案例),还是在深度、密集的树中。
如果确实存在性能问题,首先考虑 UI 是否提供良好的用户体验,如不提供,则进行更改。例如,用户很少会浏览5000行原始表格数据,并且用户更有可能使用搜索功能,该功能仅返回最相关的几个项目。
如果基于设计的解决方案不可行,并且你必须优化具有大量 DOM 元素的 UI,请在最大数组的父节点上应用 onbeforeupdate
并重新评估性能。在绝大多数情况下,单个检查应该足够了。在极少数情况下,如果不是,请重复执行,但你应该越来越警惕每个新的 onbeforeupdate
声明。多个 onbeforeupdate
是代码问题,表明设计工作流程的优先级存在问题。
避免“以防万一”而将此优化应用于应用程序的其他区域。请记住,一般来说,更多的代码比更少的代码产生更高的维护成本,并且如果你依赖对象标识进行条件检查,则与 onbeforeupdate
相关的错误可能特别难以排除故障。
再次强调,onbeforeupdate
钩子应该只作为最后的手段使用。