在处理动态 DOM 布局时,这是一个常见的问题,其中一些可写的 DOM 属性是从其他可读的 DOM 属性派生的。这在像 Mithril 这样的声明性虚拟 DOM 习惯用法中尤其难以推理,因为它们基于这样一个前提,即每个视图函数都应该是 UI 状态的自完成快照——在这种情况下这是不可能的。
您有 3 个选择:您可以通过直接在 Mithril 的视图之外操作 DOM 来打破虚拟 DOM 惯用语来实现此功能,或者您可以对组件建模以在“2 pass draw”上操作,其中每个对 header 元素的潜在更改会导致 1 次绘制来更新标题,而第二次绘制来相应地更新内容。或者,您也许可以使用纯 CSS 解决方案。
因为您只需要更新一个属性,所以几乎可以肯定选择第一个选项会更好。通过使用config 函数,您可以编写自定义功能,在每次绘制时在视图之后执行。
return m('.body', {
style: {
height: '312px'
},
config : function( el ){
el.lastChild.style.height = ( 312 - el.firstChild.offsetHeight ) + 'px'
}
}, [
m('.header', /* header contents */),
m('.content', /* some contents */)
])
第二个选项在虚拟 DOM 哲学方面更为惯用,因为它避免了直接的 DOM 操作,并将所有有状态的数据保存在模型中,并由视图读取和应用。当您拥有大量与动态 DOM 相关的属性时,这种方法会变得更加有用,因为您可以在渲染视图时检查整个视图模型 — 但它也更加复杂和低效,尤其是对于您的场景:
controller : function(){
this.headerHeight = 0
},
view : function( ctrl ){
return m('.body', {
style: {
height: '312px'
}
}, [
m('.header', {
config : function( el ){
if( el.offsetHeight != ctrl.headerHeight ){
ctrl.headerHeight = el.offsetHeight
window.requestAnimationFrame( m.redraw )
}
}, /* header contents */),
m('.content', {
style : {
height : ( 312 - ctrl.headerHeight ) + 'px'
}
}, /* some contents */)
])
}
第三种选择——depending on which browsers you need to support——是使用 CSS flexbox module。
return m('.body', {
style: {
height: '312px',
display: 'flex',
flexDirection: 'column'
}
}, [
m('.header', {
style : {
flexGrow: 1,
flexShrink: 0
}
}, /* header contents */),
m('.content', {
style : {
flexGrow: 0,
flexShrink: 1
}
}, /* some contents */)
])
这样,您可以简单地声明容器是一个 flexbox,标题应该增长以适应其内容并且永远不会缩小,并且内容应该缩小但永远不会增长。