nop*_*ole 1 reactjs react-spring
我认为react-springuseSpring()会导致组件重新渲染很多,所以如果它是一个已经有大量CPU密集工作要做的组件,那么react-spring并不是最适合该任务的。
我可以看到它重新渲染了很多,在他们的例子中:
https://codesandbox.io/s/musing-dew-9tfi9?file=/src/App.tsx
(通过查看 console.log 输出,其中有很多打印输出renderCount。当我们将持续时间更改为 5 秒时,打印输出会更多5000)。
同样,如果它是一个类似于react-spring的组件,它会渲染很多:
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>
}
Run Code Online (Sandbox Code Playgroud)
演示:https://codesandbox.io/s/dazzling-rgb-j2bx3? file=/src/App.tsx
我们可以看到renderCount几乎没有被打印出来。 react-spring应该需要继续更新style组件,所以一分钟后,我预计会像上面的前两个示例一样打印出很多内容renderCount,但事实并非如此。
在这种情况下,react-spring 如何以及为什么不会导致大量重新渲染?我们如何知道在什么情况下,react-spring 会导致大量重新渲染(以及如何防止它)?
react-spring增量更新样式以创建动画(与 css 动画相反)transition)。
如果react-spring它存在于 React 之外(显然它不存在,因为那样它就不会被命名react-spring),那么这可以通过 Javascript 根据基于多个因素的某种预定模式修改给定元素的样式来轻松完成(例如延迟、持续时间等......)。一种情况可能是
...\nsetTimeout(() => document.getElementById("#el").style.opacity = 0.34,100)\nsetTimeout(() => document.getElementById("#el").style.opacity = 0.39,150)\nsetTimeout(() => document.getElementById("#el").style.opacity = 0.42,200)\n...\nsetTimeout(() => document.getElementById("#el").style.opacity = 1.0, 1000)\nRun Code Online (Sandbox Code Playgroud)\n当然,具体如何实现这不是这个答案的重点,上面的实现将是一个非常幼稚的实现,但这基本上是如果我们想要进行一些动画过渡(其中计算两个端点之间的插值)时可能会发生的情况并由我们自己实现(使用 spring 物理),而不是在浏览器中(使用 csstransition)。
在 React 中,我们知道首选的处理方式是在 React 内部提供更改,然后 React 进行处理,然后由 React 处理对 DOM 的必要更改。以 React 为例,这意味着某种方案,其中存储的状态opacity将被重复更新,直到达到所需的端点。
const Component = () => {\n ...\n const [opacity, setOpacity] = useState(0)\n\n useEffect(() => {\n ...\n setTimeout(() => setOpacity(0.34),100)\n setTimeout(() => setOpacity(0.39),150)\n setTimeout(() => setOpacity(0.42),200)\n ...\n setTimeout(() => setOpacity(1.0), 1000)\n }, [])\n\n return (\n <div style={{ opacity }} ... />\n )\n}\nRun Code Online (Sandbox Code Playgroud)\n这是可行的,但正如人们所期望的那样,它可能会非常繁重,因为动画应该快速而流畅地发生,并且在每个动画帧上进行 React 重新渲染可能会出现问题;如果发生动画的组件的渲染成本很高,则动画本身可能会受到影响并且看起来不太好。
\nreact-spring在反应中这个问题的解决方案react-spring是通过 refs 在 React 之外进行更新。前面的玩具示例可能如下所示:
const Component = () => {\n ...\n const ref = useRef(null)\n\n useEffect(() => {\n if(ref.current) {\n ...\n setTimeout(() => ref.current.style.opacity = 0.34,100)\n setTimeout(() => ref.current.style.opacity = 0.39,150)\n setTimeout(() => ref.current.style.opacity = 0.42,200)\n ...\n setTimeout(() => ref.current.style.opacity = 1.0, 1000)\n }\n }, [])\n ...\n return (\n <div ref={ref} ... />\n )\n}\nRun Code Online (Sandbox Code Playgroud)\n同样,这只是一个示例,具体如何以最佳方式实现这一点(如 中所示react-spring)则是另一回事。但我们可以同意,如果每次渲染上述组件时都登录到控制台,即使不透明度会继续变化,它也只会记录一次。
总而言之,当react-spring最佳使用时,它使用 refs 来更新 DOM 元素上的属性,从而绕过 React。因此,组件可能只渲染一次,但仍然会制作重复的动画。这尤其适用于使用 api 执行更新的情况(与在父组件中存储状态相反,每次我们想要动画发生时都会设置该状态):
const [spring, api]\xc2\xa0= useSpring(() => ({ <INITIAL PROPS> })) // use api to make changes\nconst spring = useSpring({ <INITIAL PROPS }) // rerender component to update props\nRun Code Online (Sandbox Code Playgroud)\n当使用由(react-spring例如等)提供的基本 HTML 元素时,负责在相应的 DOM 元素上附加一个 ref,并通过此 ref,它设法为该元素以及其中的所有内容设置动画。当创建包含在 中的自定义组件时,如果您想要最佳动画,您需要确保您的自定义组件可以获取 ref (通过)并将其传递给应该动画的元素。如果您不这样做,该元素将在每个动画帧上重新渲染。尽管这也有效,但从性能的角度来看,它并不是最佳的。animated.divanimated.spanreact-springanimatedforwardRefreact-spring
在您的示例中,还有其他一些因素在起作用。useMeasure在第一个示例中,从 中使用了钩子react-use-measure。这个钩子将不断地从子组件提供不同的值(这里提供了高度),然后父组件将重新渲染。由于Tree组件是嵌套的,每当一个Tree组件更改高度时,所有Tree高度将更改的父组件也将重新渲染。因此,我们看到了相当多的重新渲染。另外,由于StrictMode启用,数量会增加一倍。useSpring在没有 api 的情况下使用,但这里并不重要,因为父级由于以下原因重新渲染了很多useMeasure。
在第二个示例中react-spring,也没有使用 api,但由于动画是循环的,因此不需要在父级中设置任何状态,因此它不会重新渲染。因为父级不会重新渲染,所以animated组件也不会重新渲染,因此在这种情况下,我们是否使用 api 并不重要。在此示例中,如果我们想要更新动画道具,那么使用 api 执行此操作将导致父级不重新渲染,否则,保存要更新的道具的状态将驻留在父级(或祖父母)中因此animated当父组件重新渲染时,组件也会重新渲染。
| 归档时间: |
|
| 查看次数: |
2000 次 |
| 最近记录: |