Mithril:渲染一个 DOM 元素基于另一个

Mithril: render one DOM element bases on another

我有一个固定高度 div (body) 包含两个 children、header 和内容。 header 的高度在单击按钮时发生变化,内容的高度应自动调整以填充 body 的其余部分。现在的问题是,新的 header 的高度是在 header 和内容 div 渲染之后计算的,所以内容 div 的高度不会单击按钮后更新。这是缩短的代码:

return m('.body', {
  style: {
    height: '312px'
  }
}, [
  m('.header', /* header contents */),
  m('.content', {
    style: {
      height: (312 - this._viewModel._headerHeight()) + 'px'
    }
  }, /* some contents */)
])

headerHeight 函数计算 header 的高度并对其应用更改。然而,新高度是在呈现后计算的,因此不会立即应用于内容高度的计算 - 总是存在滞后。

有解决办法吗?

这是处理动态 DOM 布局时的常见问题,其中一些可写 DOM 属性派生自其他可读 DOM 属性。这在像 Mithril 这样的声明性虚拟 DOM 习语中尤其难以推理,因为它们基于这样的前提,即每个视图函数都应该是 UI 状态的 self-complete 快照——在这种情况下不可能。

您有 3 个选择:您可以通过直接在 Mithril 视图之外操作 DOM 来突破虚拟 DOM 习语来实现此功能,或者您可以对组件进行建模以进行操作在“2 pass draw”上,header 元素的每个潜在更改都会导致 1 次绘制以更新 header 和第二次绘制以相应地更新内容。或者,您可以使用纯粹的 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-related 属性时,这种方法会变得更有用,因为您可以在呈现视图时检查整个视图模型——但它也更加复杂和低效,尤其是对于您的场景:

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 — would be to use the 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,header 应该增长以适应其内容并且永不收缩,并且内容应该收缩但永不增长。