对这个奇怪的问题非常抱歉。 我期待用反应动作创建一个短弹簧动画。 请耐心等待:
我有一个组件,根据选定的基本目录,加载框架内的本地目录和文件列表,有点像mac finder窗口。因此,根据任意目录,获取的文件数量会有所不同,因此我的帧的高度也会变化(直到达到最大高度)。
现在,只要获取了所选目录的内容,我就想要动态地将此组件的框架打开到正确的高度(或最大高度)。如果我要更改目录,我需要根据提取的元素数量或直接到最大高度来弹出/调整帧高度。
以下是该组件的相关摘录:
<Motion
defaultStyle={{x: 30}}
style={{x: spring(330, {stiffness: 270, damping: 17})}}
>
{({x}) =>
<List height={x}>
{
localTree.map(item =>
<ListItem key={v4()}>
<FileIcon isDirectory={item.isDirectory} />
<span onClick={() => this.handleClick(item.id)}>
{item.baseName}
</span>
</ListItem>
)
}
</List>
}
</Motion>
您可以看到,目前defaultStyle
和style
变量是静态设置的。它运行正常,特别是因为我已将<List />
组件的样式设置为overflow: scroll
,因此如果在这种情况下有更多元素而不是330px
,则稍微滚动会使其自然可浏览。 / p>
但这不是我最终想要的。
我希望defaultStyle={{x: 30}}
和style={{x: spring(330, )}}
由累积的<ListItem />
子元素数的高度动态设置。
我该如何解决这个问题?我应该在某处添加父组件吗?
答案 0 :(得分:2)
这是一个非常有趣的问题,非常鸡肉和鸡蛋,你如何测量需要渲染的东西的高度,以便测量它,而不会让用户首先看到它闪烁。对此的不良解决方案可能涉及将某些内容从屏幕上移除并进行测量,但这与React推广的概念并不相符。
然而,不仅可以通过了解组件生命周期,还可以了解它如何适应浏览器生命周期来解决。
关键是componentDidMount
和componentDidUpdate
。一个常见的误解是,这些是在组件被渲染到浏览器之后发生的,而不是真的,它会在发生之前触发
render
将首先将虚拟DOM与您要呈现的任何内容进行协调(此处的另一个误解 - 虚拟DOM实际上不是真实的甚至是影子DOM,它只是表示组件的JS对象树和算法可以导出差异的DOM - 你无法确定动态高度或此时与真实DOM元素交互。)
如果它感觉到要进行实际的DOM突变,那么componentDidMount
/ componentDidUpdate
现在会在实际的浏览器渲染之前触发。
然后它将执行对真实DOM的派生更新(有效rendering
它到浏览器DOM)。至关重要的是,浏览器生命周期意味着这个渲染的DOM还没有被绘制到屏幕上(即从用户的角度看它不是rendered
),直到requestAnimationFrame
有componentWillReceiveProps
|
V
shouldComponentUpdate
|
V
componentWillUpdate
|
V
render
|
V
componentDidUpdate
|
V
[BROWSER DOM rendered - can be interacted with programatically]
|
V
requestAnimationFrame callback
|
V
[BROWSER screen painted with updated DOM - can be seen and interacted by user]
时的下一个勾号有机会拦截它......
componentDidX
所以在window.requestAnimationFrame
的这一点上,我们有机会告诉它在绘制之前拦截浏览器,但我们可以与真正更新的DOM进行交互并执行测量!为此,我们需要使用componentWillUnmount
,它采用回调函数,我们可以在其中执行测量。由于这是一个异步函数,它返回一个id,您可以使用它来取消它,就像setTimeout一样(我们应该记得从ref
清除所有待处理的请求)。
我们还需要一个可测量的父元素,它不会在其上设置样式,并存储div
,因此我们可以到DOM节点进行测量。还有一个高度动画的包装。
简化而没有反应运动(但外部Motion
可以包裹在 // added type info for better clarity
constructor(props: SomeProps) {
super(props)
this.state = {
height: undefined
}
}
listRef: HTMLElement
bindListRef = (el: HTMLElement): void => {
this.listRef = el
}
rafId: number
interceptAndMeasure = (): void {
// use request animation frame to intercept and measure new DOM before the user sees it
this.rafId = window.requestAnimationFrame(this.onRaf)
}
onRaf = (): void => {
const height: number = this.listRef.getBoundingClientRect().height
// if we store this in the state - the screen will not get painted, as a new complete render lifecycle will ensue
// BEWARE you must have a guard condition against setting state unnecessarily here
// as it is triggered from within `componentDidX` which can if unchecked cause infinite re-render loops!
if (height !== this.state.height) this.setState({height})
}
render() {
const {data} = this.props
const {height} = this.state.height
// the ul will be pushed out to the height of its children, and we store a ref that we can measure it by
// the wrapper div can have its height dynamically set to the measured value (or an interpolated interim animated value!)
return (
<div style={height !== undefined ? {height: `${height}px`} : {}}>
<ul ref={this.bindListRef}>
{data.map((_, i) =>
<li key={i}>
{_.someMassiveText}
</li>
)}
</ul>
</div>
)
}
componentDidMount() {
this.interceptAndMeasure()
}
componentDidUpdate() {
this.interceptAndMeasure()
}
componentWillUnmount() {
// clean up in case unmounted while still pending
window.cancelAnimationFrame(this.rafId)
}
中并且可以将插值插入动态样式道具中... ...
react-collapse
当然以前遇到过这种情况,幸运的是,你可以不必担心自己实现它并使用使用react-motion
的优秀newmerge.replace([np.var=='', np.nan], [True, False])
Company Weight IsinSP100?
Symbol
AAPL Apple Inc. 3.699828 AAPL
MSFT Microsoft 2.686835 MSFT
AMZN Amazon.com Inc. 1.901109 AMZN
AN AutoNation Inc. 0.013148 False
NWS News Corporation 0.007284 False
包(https://github.com/nkbt/react-collapse)在表面下面做这个,并且还处理许多边缘情况,如嵌套动态高度等
但在此之前,我真的恳请您至少自己尝试一下,因为它可以更深入地了解React和浏览器生命周期,并将有助于您将很难实现的深度互动体验否则