我认为react spring(useSpring)会导致组件大量重新渲染,但可能不会,我们如何做到这一点



我认为react springuseSpring()会导致组件大量重新渲染,所以如果它是一个已经有大量CPU密集工作要做的组件,那么react spring就不适合执行该任务。

我可以看到它重新渲染了很多,在他们的例子中:

https://codesandbox.io/s/musing-dew-9tfi9?file=/src/App.tsx

(通过查看console.log输出,它有很多renderCount的打印输出。当我们将持续时间更改为5秒的5000时,打印输出要多得多)。

同样,如果它是一个类似于反作用弹簧的组件,它会渲染很多:

https://codesandbox.io/s/wonderful-currying-9pheq

但是,以下代码:

let renderCount = 0
export default function App() {
const styles = useSpring({
loop: true,
to: [
{ opacity: 1, color: '#ffaaee' },
{ opacity: 0.5, color: 'rgb(14,26,19)' },
{ transform: 'translateX(100px)' },
{ transform: 'translateX(0px)' },
],
from: { opacity: 0, color: 'red', transform: 'translateX(0px)' },
config: { duration: 500 },
})
console.log('renderCount', ++renderCount)
return <a.div style={styles}>I will fade in and out</a.div>
}

演示:https://codesandbox.io/s/dazzling-rgb-j2bx3?file=/src/App.tsx

我们可以看到renderCount几乎没有被打印出来。react-spring应该需要不断更新组件的style,所以一分钟后,我会像上面的前两个例子一样,期待renderCount的大量打印出来,但事实并非如此。

在这种情况下,react spring是如何以及为什么不导致大量重新渲染的,我们如何知道在什么情况下react spring会导致大量重新绘制(以及如何防止)?

react-spring以增量方式更新样式以创建动画(与使用transition的css动画相反)。

React之外的天真动画

如果react-spring存在于React之外(它显然不存在,因为那样它就不会被命名为react-spring),那么最容易做到的是,根据基于多个因素(如延迟、持续时间等)的一些预先确定的模式,通过Javascript修改给定元素的样式

...
setTimeout(() => document.getElementById("#el").style.opacity = 0.34,100)
setTimeout(() => document.getElementById("#el").style.opacity = 0.39,150)
setTimeout(() => document.getElementById("#el").style.opacity = 0.42,200)
...
setTimeout(() => document.getElementById("#el").style.opacity = 1.0, 1000)

这将如何实现当然不是这个答案的重点,上面的实现将是一个非常天真的实现,但如果我们想进行一些动画转换,其中两个端点之间的插值将由我们自己计算和实现(使用弹簧物理),而不是在浏览器中(使用csstransition),这基本上就是可以进行的。

React中的天真动画

在React中,我们知道做事情的首选方式是在React内部提供更改,然后React处理这些更改,然后由React处理对DOM的必要更改。以前面的React(幼稚)示例为例,这意味着某种方案,其中存储opacity的状态将被重复更新,直到达到所需的端点。

const Component = () => {
...
const [opacity, setOpacity] = useState(0)
useEffect(() => {
...
setTimeout(() => setOpacity(0.34),100)
setTimeout(() => setOpacity(0.39),150)
setTimeout(() => setOpacity(0.42),200)
...
setTimeout(() => setOpacity(1.0), 1000)
}, [])
return (
<div style={{ opacity }} ... />
)
}

这是可行的,但正如人们所料,这可能会非常繁重,因为动画应该快速平滑地进行,并且在每个动画帧上重新渲染React可能会有问题;如果发生动画的组件渲染成本很高,则动画本身可能会受到影响,看起来不太好。

React中的react-spring

react-spring解决这个问题的方法是通过refs在React的外部进行更新。前面的玩具示例可能看起来像:

const Component = () => {
...
const ref = useRef(null)
useEffect(() => {
if(ref.current) {
...
setTimeout(() => ref.current.style.opacity = 0.34,100)
setTimeout(() => ref.current.style.opacity = 0.39,150)
setTimeout(() => ref.current.style.opacity = 0.42,200)
...
setTimeout(() => ref.current.style.opacity = 1.0, 1000)
}
}, [])
...
return (
<div ref={ref} ... />
)
}

同样,这是一个示例,如何以最佳方式(如react-spring)实现这一点则另当别论。但我们可以同意,如果我们每次渲染上述组件时都会登录到控制台,那么即使不透明度会继续变化,它也只会登录一次。

总之,当最佳使用react-spring时,它会使用refs来更新DOM元素的属性,从而绕过React。因此,组件可能只渲染一次,但仍会生成重复的动画。这尤其适用于api用于执行更新的情况(而不是将状态存储在父组件中,每次我们希望动画发生时都会设置该状态):

const [spring, api] = useSpring(() => ({ <INITIAL PROPS> })) // use api to make changes
const spring = useSpring({ <INITIAL PROPS }) // rerender component to update props

当使用react-spring提供的基本HTML元素(如animated.divanimated.span等)时,react-spring负责在相应的DOM元素上附加一个ref,并通过这个ref来管理元素的动画化,从而也动画化其中的所有内容。当创建用animated包装的自己的自定义组件时,如果您想要最佳的动画效果,您需要确保您的自定义组件可以接受ref(通过forwardRef),并将其传递给应该设置动画的元素。如果不执行此操作,则react-spring将在每个动画帧上重新渲染该元素。尽管这也很有效,但从性能的角度来看,它是次优的。

你的例子

在你的例子中,其他一些事情也在起作用。在第一个例子中,钩子useMeasure是从react-use-measure开始使用的。这个钩子将持续提供与子组件不同的值(此处提供高度),因此父组件将重新应答。由于Tree组件是嵌套的,因此每当一个Tree组件更改高度时,其高度将更改的所有父Tree组件也将重新发布。因此,我们看到了相当多的重新招标。此外,由于启用了StrictMode,因此该数字会加倍。useSpring是在没有api的情况下使用的,但这在这里并不重要,因为无论如何,由于useMeasure,父级会重新发送很多。

react-spring的第二个示例中,也没有使用api,但由于动画是循环的,因此不需要设置父对象中的任何状态,因此不会重新渲染。因为父级不重新发布,animated组件也不重新发布——所以在这种情况下,我们是否使用api也无关紧要。然而,在本例中,如果我们想更新动画道具,那么使用api会导致父级不重新发布,否则,持有要更新的道具的状态将位于父级(或父级),因此当父级重新发布时,animated组件也会重新发布。

相关内容

  • 没有找到相关文章

最新更新