react-spring 如何使用 animated(Component)
react-spring how to use animated(Component)
我正在尝试解决图像滑块中的一些性能问题,我发现使用 animated.img
比使用 animated.div
和内部一些反应组件产生更好的性能。
react 组件显然不仅仅是为了好玩而放在里面,但幸运的是 react-spring 允许您通过执行
来为自定义组件设置动画
const AnimatedComponent = animated(Component)
根据the docs
但是我该如何使用它呢?我一直在尝试,但打字稿只是给出了一些关于缺少 269 种不同类型道具的非常无用的信息。
编辑 添加错误
vscode 显示打字稿错误,但这可能并不重要。由于我不知道要传递什么道具才能为组件设置动画,所以我对它不起作用并不感到惊讶,但错误消息并不能真正帮助我确定我需要做什么。
' is missing the following properties from type 'AnimatedProps<{ title: string | FluidValue<string, any>; id: string | FluidValue<string, any>; article?: { title: string; metaTitle: string; metaDescription: string; description: string; showRelatedArticles: boolean; elements: ({ ...; } | ... 4 more ... | { ...; })[]; } | null | undefined; ... 269 more ...; key?: st...': title, id, slot, animate, and 257 more.ts(2740)
我删除了一些第一个道具,因为我从我试图制作动画的组件中认出了它们,并且我知道它们存在。
有人试过用这个吗?一个如何使用它的例子会非常好。
如果重要的话,我正在使用 9.0.0-rc.3
版本的 react-spring。
只是为了开始对话。
让我们从文档的示例开始。假设您有一个第三方 Donut 组件。它有一个百分比 属性。并且您想基于此 属性 制作动画。所以你可以使用动画作为甜甜圈的包装。
const AnimatedDonut = animated(Donut)
// ...
const props = useSpring({ value: 100, from: { value: 0 } })
return <AnimatedDonut percent={props.value} />
是哪里出问题了?
背景
react-spring
不像 css 转换 api 那样依赖于时间,而是从物理环境中进行转换和动画。为了在 React 中以可接受的性能实现这一点,它绕过 React 并对相关 DOM 节点本身进行修改。
常规react-spring
动画组件
如您所见,所有常规 DOM 节点都作为 react-spring
等价物存在。例如 animation.span
、animation.div
等...这些将本机 DOM 元素包装为 react-spring
工作所需的必要功能。这里值得注意的是这两个微妙之处:
react-spring
的功能附加到单个 DOM 节点
- 因为该功能附加到本机 DOM 节点,所以仅使用本机 DOM 元素属性
这两个事实对我们如何使用包装在 animated
中的自定义组件都有影响。
我们的自定义组件
让我们使用 React 功能组件和 Typescript 处理一个简单的场景,看看如何将其转换为自定义 react-spring
组件。
假设您有一个 div
,您希望在单击它后从一种颜色过渡到另一种颜色时为其背景颜色设置动画。
1。没有 react-spring
基本方法是:
const Comp: FC = () => {
const [color, setColor] = useState<string>("green")
return (
<div
style={{
backgroundColor: color,
transition: "background-color 1s"
}}
onClick={ () => setColor(color => color === "blue" ? "green" : "blue") }
/>
)
}
2。 react-spring
基本用法
对 useSpring
的 react-spring
基本用法做同样的事情会导致
const Comp: FC = () => {
const [color, setColor] = useState<string>("green")
const springColor = useSpring({ backgroundColor: color})
return (
<animated.div
style={springColor}
onClick={ () => setColor(color => color === "blue" ? "green" : "blue") }
/>
)
}
3。 react-spring
最佳实践用法
更好的方法是使用 api functions 这样我们就不必在每次颜色更改时都重新渲染组件。需要明确的是,当您使用此方法时,您不会更改任何传递给要设置动画的组件的道具,因此您可以通过 api 更改其状态而无需重新渲染它,只要 Comp
本身不会重新渲染。
const Comp: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }))
return (
<animated.div
style={springColor}
onClick={ () => api.start({ backgroundColor: springColor.backgroundColor.goal === "blue" ? "green" : "blue" })}
/>
)
}
4。委派给 class
让我们考虑一下。您正在将一些包装的 属性 传递给这些 animated
组件。这些属性属于 SpringValue<T>
类型,它们可以通过 new
或例如 useSpring
实例化。我们构建自定义组件的第一步是简单地将这些作为属性传递给其中包含 animated
组件的组件:
export interface CompProps {
color: SpringValue<string>;
onChangeColor: () => void;
}
const Comp: FC<CompProps> = (props: CompProps) => {
return (
<animated.div
style={{ backgroundColor: props.color }}
onClick={props.onChangeColor}
/>
)
}
const Parent: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }));
return (
<Comp
color={springColor.backgroundColor}
onChangeColor={() => api.start({
backgroundColor: springColor.backgroundColor.goal === "blue" ? "green" : "blue"
})}
/>
)
}
5。使用包裹在 animated
中的天真的自定义组件
现在我们准备好进行替换并将我们的 属性 包装在 animated
中,而不是在我们的组件中使用 animated
本机元素。
export interface CompProps {
style: CSSProperties;
onChangeColor: () => void;
}
const Comp: FC<CompProps> = (props: CompProps) => {
return (
<div
style={props.style}
onClick={props.onChangeColor}
/>
)
}
const WrappedComp: AnimatedComponent<FC<CompProps>> =
animated(Comp)
const Parent: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }));
return (
<WrappedComp
style={{ backgroundColor: springColor.backgroundColor }}
onChangeColor={() =>
api.start({
backgroundColor:
springColor.backgroundColor.goal === "blue" ? "green" : "blue"
})
}
/>
)
}
请注意,现在我们的包装组件看起来像一个常规组件,并且没有显示与 react-spring
一起使用的迹象。尽管如此,正如我们将要看到的,为了与 react-spring
的集成按预期工作,仍然有一些额外的要求。请注意,我们如何不再提供 backgroundColor
作为道具,而是使用 style
。此外,传递给我们的自定义组件的所有动画道具都是在我们的自定义组件附加转发引用的原生 div
元素上可用的道具。更多关于这个的进一步下降。
上面的组件很幼稚,因为 animated
包装器无法在不重新渲染的情况下更新包装的组件。为什么?仅仅是因为我们包装的组件不接受引用,所以要求 react-spring
能够在不重新渲染的情况下更新我们的组件是不合理的。
6.使用包含在 animated
中的适当自定义组件
让我们通过让它接受引用来增强我们的包装组件。
const Comp: FC<CompProps & RefAttributes<HTMLDivElement>> =
forwardRef<HTMLDivElement,CompProps>(
(props, ref) => {
return (
<div
ref={ref}
style={props.style}
onClick={props.onChangeColor}
/>
)
}
)
现在 react-spring
会感觉到我们包装的组件接受了一个 ref,因此它将避免在每次更改时重新渲染它,而是使用 ref 来更新它。因为更新是通过 ref 进行的,所以 props 是我们要更改的元素的实际 props 很重要;如果 React 需要将自定义道具映射到 DOM 元素上的实际道具,那么我们需要重新渲染每个新的动画值,这是次优的。尽管如此,当我们不让我们的自定义组件接受引用时,这正是会发生的事情。因此,即使我们继续使用自定义属性 backgroundColor
而不是 style
,版本 5 也能正常工作。然而,版本号 6 不起作用,属性 backgroundColor
将简单地添加到设置 ref 的 DOM 元素中,这不会导致任何更改,因为此属性 不是原生 div
DOM 元素上的 属性。
沙盒
我已经制作了两个可用的沙箱:
第一个沙箱显示了这个答案中的组件。每个组件在呈现时都会在控制台中打印输出。检查这些打印输出并验证上述行为。 Sandbox
第二个沙盒主题相同,但稍微高级一些。这里渲染的数量是保持最新的,这样我们就可以验证不同的行为。此沙盒中的 take-aways 之一是我们使用 api 所做的所有动画更改都是“免费”的,因为它不会增加受影响组件的渲染数量。当 parent 像往常一样重新渲染时,所有 children 也会重新渲染。底部添加了要点列表。 Sandbox
结论
- 在使用
react-spring
钩子时始终使用 api 方法。
- 在
animated
. 中包装组件时,始终让它们接受 ref
- 如果您希望能够在不重新渲染的情况下更新您的包装组件,您只能将
react-spring
的行为附加到一个元素。 react-spring
使用 ref 进行更新,因为你不能同时将 ref 附加到多个元素(forwardRef
也没有提供添加多个 ref 的方法),你不能使用你的在包装组件中的多个位置弹出而不重新渲染它,否则您将无法获得预期的功能。对于这样的组件,最好将 SpringValue<T>
作为 props 传递,并在组件内部使用 animated
原生元素。
不要:
const NestedComp = ({ style1, style2 }) => {
...
return (
<div style={ style1 }>
<div style={ style2 }>
....
</div>
</div>
)
}
const Wrapped = animated(NestedComp)
做:
const NestedComp = ({ springStyle1, springStyle2 }) => {
...
return (
<animated.div style={ springStyle1 }>
<animated.div style={ springStyle2 }>
....
</animated.div>
</animated.div>
)
}
- 确保您在元素的动画中使用的自定义道具与您附加 ref 的原生 DOM 元素的道具兼容,否则
react-spring
无法正确更新直接设置元素(相反,它只是设置 属性,如果 DOM 元素上不存在,则没有任何效果)。
不要:
const Comp = ({ color }) => {
...
return (
<div style={{ backgroundColor: props.color }} />
)
}
const WrappedComp = animated(Comp)
const Parent = () => {
...
const [springProp, api] ) = useSpring(() => ({ color: "green" }))
...
return <WrappedComp color={ springProp.color } />
}
做:
const Comp = ({ style }) => {
...
return (
<div style={props.style} />
)
}
const WrappedComp = animated(Comp)
const Parent = () => {
...
const [springProp, api] ) = useSpring(() => ({ color: "green" }))
...
return (
<WrappedComp
style={{ backgroundColor: springProp.color }}
/>
)
}
在上面,我们想在不使用 React 的情况下更新 Comp
,但是 React 需要 assemble 正确的 style
属性,因为 color
不是原生的div
元素的 prop,因此通过 ref 更新 color
只会导致 color
prop 被设置在 div
元素上,什么都不做。另一方面,当我们在 parent 中添加 style
属性 时,animated
组件将检测到其中一个 style
道具是 SpringValue
并相应地更新它会达到预期的效果。
最后,请记住,如果我们不是在用 api 更新我们的自定义组件之后,我们可以简单地避免设计我们的自定义组件,以便它可以接受一个 ref 并使用我们想要的任何 prop 名称; react-spring
现在无论如何都会在每个动画帧上重新渲染组件,因此 React 会将所有自定义道具映射到正确的原生 DOM 元素道具。尽管如此,希望这种执行策略不可取。
我正在尝试解决图像滑块中的一些性能问题,我发现使用 animated.img
比使用 animated.div
和内部一些反应组件产生更好的性能。
react 组件显然不仅仅是为了好玩而放在里面,但幸运的是 react-spring 允许您通过执行
来为自定义组件设置动画const AnimatedComponent = animated(Component)
根据the docs
但是我该如何使用它呢?我一直在尝试,但打字稿只是给出了一些关于缺少 269 种不同类型道具的非常无用的信息。
编辑 添加错误
vscode 显示打字稿错误,但这可能并不重要。由于我不知道要传递什么道具才能为组件设置动画,所以我对它不起作用并不感到惊讶,但错误消息并不能真正帮助我确定我需要做什么。
' is missing the following properties from type 'AnimatedProps<{ title: string | FluidValue<string, any>; id: string | FluidValue<string, any>; article?: { title: string; metaTitle: string; metaDescription: string; description: string; showRelatedArticles: boolean; elements: ({ ...; } | ... 4 more ... | { ...; })[]; } | null | undefined; ... 269 more ...; key?: st...': title, id, slot, animate, and 257 more.ts(2740)
我删除了一些第一个道具,因为我从我试图制作动画的组件中认出了它们,并且我知道它们存在。
有人试过用这个吗?一个如何使用它的例子会非常好。
如果重要的话,我正在使用 9.0.0-rc.3
版本的 react-spring。
只是为了开始对话。
让我们从文档的示例开始。假设您有一个第三方 Donut 组件。它有一个百分比 属性。并且您想基于此 属性 制作动画。所以你可以使用动画作为甜甜圈的包装。
const AnimatedDonut = animated(Donut)
// ...
const props = useSpring({ value: 100, from: { value: 0 } })
return <AnimatedDonut percent={props.value} />
是哪里出问题了?
背景
react-spring
不像 css 转换 api 那样依赖于时间,而是从物理环境中进行转换和动画。为了在 React 中以可接受的性能实现这一点,它绕过 React 并对相关 DOM 节点本身进行修改。
常规react-spring
动画组件
如您所见,所有常规 DOM 节点都作为 react-spring
等价物存在。例如 animation.span
、animation.div
等...这些将本机 DOM 元素包装为 react-spring
工作所需的必要功能。这里值得注意的是这两个微妙之处:
react-spring
的功能附加到单个 DOM 节点- 因为该功能附加到本机 DOM 节点,所以仅使用本机 DOM 元素属性
这两个事实对我们如何使用包装在 animated
中的自定义组件都有影响。
我们的自定义组件
让我们使用 React 功能组件和 Typescript 处理一个简单的场景,看看如何将其转换为自定义 react-spring
组件。
假设您有一个 div
,您希望在单击它后从一种颜色过渡到另一种颜色时为其背景颜色设置动画。
1。没有 react-spring
基本方法是:
const Comp: FC = () => {
const [color, setColor] = useState<string>("green")
return (
<div
style={{
backgroundColor: color,
transition: "background-color 1s"
}}
onClick={ () => setColor(color => color === "blue" ? "green" : "blue") }
/>
)
}
2。 react-spring
基本用法
对 useSpring
的 react-spring
基本用法做同样的事情会导致
const Comp: FC = () => {
const [color, setColor] = useState<string>("green")
const springColor = useSpring({ backgroundColor: color})
return (
<animated.div
style={springColor}
onClick={ () => setColor(color => color === "blue" ? "green" : "blue") }
/>
)
}
3。 react-spring
最佳实践用法
更好的方法是使用 api functions 这样我们就不必在每次颜色更改时都重新渲染组件。需要明确的是,当您使用此方法时,您不会更改任何传递给要设置动画的组件的道具,因此您可以通过 api 更改其状态而无需重新渲染它,只要 Comp
本身不会重新渲染。
const Comp: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }))
return (
<animated.div
style={springColor}
onClick={ () => api.start({ backgroundColor: springColor.backgroundColor.goal === "blue" ? "green" : "blue" })}
/>
)
}
4。委派给 class
让我们考虑一下。您正在将一些包装的 属性 传递给这些 animated
组件。这些属性属于 SpringValue<T>
类型,它们可以通过 new
或例如 useSpring
实例化。我们构建自定义组件的第一步是简单地将这些作为属性传递给其中包含 animated
组件的组件:
export interface CompProps {
color: SpringValue<string>;
onChangeColor: () => void;
}
const Comp: FC<CompProps> = (props: CompProps) => {
return (
<animated.div
style={{ backgroundColor: props.color }}
onClick={props.onChangeColor}
/>
)
}
const Parent: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }));
return (
<Comp
color={springColor.backgroundColor}
onChangeColor={() => api.start({
backgroundColor: springColor.backgroundColor.goal === "blue" ? "green" : "blue"
})}
/>
)
}
5。使用包裹在 animated
中的天真的自定义组件
现在我们准备好进行替换并将我们的 属性 包装在 animated
中,而不是在我们的组件中使用 animated
本机元素。
export interface CompProps {
style: CSSProperties;
onChangeColor: () => void;
}
const Comp: FC<CompProps> = (props: CompProps) => {
return (
<div
style={props.style}
onClick={props.onChangeColor}
/>
)
}
const WrappedComp: AnimatedComponent<FC<CompProps>> =
animated(Comp)
const Parent: FC = () => {
const [springColor, api] = useSpring(() => ({ backgroundColor: "green" }));
return (
<WrappedComp
style={{ backgroundColor: springColor.backgroundColor }}
onChangeColor={() =>
api.start({
backgroundColor:
springColor.backgroundColor.goal === "blue" ? "green" : "blue"
})
}
/>
)
}
请注意,现在我们的包装组件看起来像一个常规组件,并且没有显示与 react-spring
一起使用的迹象。尽管如此,正如我们将要看到的,为了与 react-spring
的集成按预期工作,仍然有一些额外的要求。请注意,我们如何不再提供 backgroundColor
作为道具,而是使用 style
。此外,传递给我们的自定义组件的所有动画道具都是在我们的自定义组件附加转发引用的原生 div
元素上可用的道具。更多关于这个的进一步下降。
上面的组件很幼稚,因为 animated
包装器无法在不重新渲染的情况下更新包装的组件。为什么?仅仅是因为我们包装的组件不接受引用,所以要求 react-spring
能够在不重新渲染的情况下更新我们的组件是不合理的。
6.使用包含在 animated
中的适当自定义组件
让我们通过让它接受引用来增强我们的包装组件。
const Comp: FC<CompProps & RefAttributes<HTMLDivElement>> =
forwardRef<HTMLDivElement,CompProps>(
(props, ref) => {
return (
<div
ref={ref}
style={props.style}
onClick={props.onChangeColor}
/>
)
}
)
现在 react-spring
会感觉到我们包装的组件接受了一个 ref,因此它将避免在每次更改时重新渲染它,而是使用 ref 来更新它。因为更新是通过 ref 进行的,所以 props 是我们要更改的元素的实际 props 很重要;如果 React 需要将自定义道具映射到 DOM 元素上的实际道具,那么我们需要重新渲染每个新的动画值,这是次优的。尽管如此,当我们不让我们的自定义组件接受引用时,这正是会发生的事情。因此,即使我们继续使用自定义属性 backgroundColor
而不是 style
,版本 5 也能正常工作。然而,版本号 6 不起作用,属性 backgroundColor
将简单地添加到设置 ref 的 DOM 元素中,这不会导致任何更改,因为此属性 不是原生 div
DOM 元素上的 属性。
沙盒
我已经制作了两个可用的沙箱:
第一个沙箱显示了这个答案中的组件。每个组件在呈现时都会在控制台中打印输出。检查这些打印输出并验证上述行为。 Sandbox
第二个沙盒主题相同,但稍微高级一些。这里渲染的数量是保持最新的,这样我们就可以验证不同的行为。此沙盒中的 take-aways 之一是我们使用 api 所做的所有动画更改都是“免费”的,因为它不会增加受影响组件的渲染数量。当 parent 像往常一样重新渲染时,所有 children 也会重新渲染。底部添加了要点列表。 Sandbox
结论
- 在使用
react-spring
钩子时始终使用 api 方法。 - 在
animated
. 中包装组件时,始终让它们接受 ref
- 如果您希望能够在不重新渲染的情况下更新您的包装组件,您只能将
react-spring
的行为附加到一个元素。react-spring
使用 ref 进行更新,因为你不能同时将 ref 附加到多个元素(forwardRef
也没有提供添加多个 ref 的方法),你不能使用你的在包装组件中的多个位置弹出而不重新渲染它,否则您将无法获得预期的功能。对于这样的组件,最好将SpringValue<T>
作为 props 传递,并在组件内部使用animated
原生元素。
不要:
const NestedComp = ({ style1, style2 }) => {
...
return (
<div style={ style1 }>
<div style={ style2 }>
....
</div>
</div>
)
}
const Wrapped = animated(NestedComp)
做:
const NestedComp = ({ springStyle1, springStyle2 }) => {
...
return (
<animated.div style={ springStyle1 }>
<animated.div style={ springStyle2 }>
....
</animated.div>
</animated.div>
)
}
- 确保您在元素的动画中使用的自定义道具与您附加 ref 的原生 DOM 元素的道具兼容,否则
react-spring
无法正确更新直接设置元素(相反,它只是设置 属性,如果 DOM 元素上不存在,则没有任何效果)。
不要:
const Comp = ({ color }) => {
...
return (
<div style={{ backgroundColor: props.color }} />
)
}
const WrappedComp = animated(Comp)
const Parent = () => {
...
const [springProp, api] ) = useSpring(() => ({ color: "green" }))
...
return <WrappedComp color={ springProp.color } />
}
做:
const Comp = ({ style }) => {
...
return (
<div style={props.style} />
)
}
const WrappedComp = animated(Comp)
const Parent = () => {
...
const [springProp, api] ) = useSpring(() => ({ color: "green" }))
...
return (
<WrappedComp
style={{ backgroundColor: springProp.color }}
/>
)
}
在上面,我们想在不使用 React 的情况下更新 Comp
,但是 React 需要 assemble 正确的 style
属性,因为 color
不是原生的div
元素的 prop,因此通过 ref 更新 color
只会导致 color
prop 被设置在 div
元素上,什么都不做。另一方面,当我们在 parent 中添加 style
属性 时,animated
组件将检测到其中一个 style
道具是 SpringValue
并相应地更新它会达到预期的效果。
最后,请记住,如果我们不是在用 api 更新我们的自定义组件之后,我们可以简单地避免设计我们的自定义组件,以便它可以接受一个 ref 并使用我们想要的任何 prop 名称; react-spring
现在无论如何都会在每个动画帧上重新渲染组件,因此 React 会将所有自定义道具映射到正确的原生 DOM 元素道具。尽管如此,希望这种执行策略不可取。