Mat*_*sen 5 javascript typescript reactjs react-spring
我正在尝试解决图像滑块中的一些性能问题,我发现使用它animated.img比使用内部的某些反应组件产生更好的性能animated.div。
React 组件显然不仅仅是为了它的乐趣而放置在里面,但幸运的是,react-spring 允许您通过执行以下操作来为自定义组件设置动画
const AnimatedComponent = animated(Component)
Run Code Online (Sandbox Code Playgroud)
根据文档
但我该如何使用它呢?我一直在尝试,但 Typescript 只是给出了一些关于缺少 269 种不同类型的道具的非常无用的信息。
编辑添加错误
vscode 显示了 typescript 错误,但这可能并不重要。由于我不知道要传递哪些道具才能为组件设置动画,因此我对它不起作用并不感到惊讶,但错误消息并不能真正帮助确定我需要做什么。
' 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)
Run Code Online (Sandbox Code Playgroud)
我剥离了一些第一个道具,因为我从我试图设置动画的组件中识别出它们,并且我知道它们存在。
有人尝试过使用这个吗?如果有一个如何使用它的例子就太好了。
9.0.0-rc.3如果这很重要的话,我正在使用react-spring的版本。
react-spring不像 css 过渡 api 那样依赖于时间,而是从物理上下文中进行过渡和动画。为了在 React 中实现可接受的性能,它绕过 React 并对相关 DOM 节点本身进行修改。
react-spring动画组件正如您可能已经看到的,所有常规 DOM 节点都作为react-spring等效节点存在。例如animation.span,等等...这些将原生 DOM 元素包装为工作animation.div所需的功能。react-spring这里值得注意的是以下两个微妙之处:
react-spring附加到单个 DOM 节点这两个事实都对我们如何使用包含在animated.
让我们使用 React 功能组件和 Typescript 来处理一个简单的场景,看看如何将其转换为自定义react-spring组件。
假设您想div在单击背景颜色后从一种颜色转换为另一种颜色时对其背景颜色进行动画处理。
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") }
/>
)
}
Run Code Online (Sandbox Code Playgroud)
react-spring基本使用方法react-spring使用 的基本用法做同样的事情useSpring会导致
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") }
/>
)
}
Run Code Online (Sandbox Code Playgroud)
react-spring最佳实践使用更好的是使用api 函数,这样我们就不必在每次颜色更改时重新渲染组件。需要明确的是,当您使用此方法时,您不会更改传递给要设置动画的组件的任何 props,因此您可以通过 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" })}
/>
)
}
Run Code Online (Sandbox Code Playgroud)
我们想一想。您将一些包装的属性传递给这些animated组件。这些属性是类型的,并且可以通过例如 来SpringValue<T>实例化它们。我们构建自定义组件的第一步是将这些作为属性简单地传递给其中包含组件的组件:newuseSpringanimated
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"
})}
/>
)
}
Run Code Online (Sandbox Code Playgroud)
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"
})
}
/>
)
}
Run Code Online (Sandbox Code Playgroud)
请注意,现在我们的包装组件看起来就像一个常规组件,并且没有显示出与 一起使用的迹象react-spring。react-spring尽管如此,正如我们将看到的,为了使集成按预期工作,仍然有一些额外的要求。请注意,我们不再backgroundColor作为道具提供,而是使用style. 此外,传递给我们的自定义组件的所有动画道具都是在div我们的自定义组件附加转发引用的本机元素上可用的道具。更多关于这一点的内容请往下看。
上面的组件很幼稚,因为animated包装器无法在不重新渲染的情况下更新包装的组件。为什么?仅仅因为我们的包装组件不接受引用,因此要求react-spring能够更新我们的组件而不重新渲染它是不合理的。
animated让我们通过让它接受引用来增强我们的包装组件。
const Comp: FC<CompProps & RefAttributes<HTMLDivElement>> =
forwardRef<HTMLDivElement,CompProps>(
(props, ref) => {
return (
<div
ref={ref}
style={props.style}
onClick={props.onChangeColor}
/>
)
}
)
Run Code Online (Sandbox Code Playgroud)
现在react-spring将感觉到我们的包装组件接受引用,因此它将避免在每次更改时重新渲染它,而是使用引用来更新它。因为更新是通过 ref 进行的,所以 props 必须是我们想要更改的元素上的实际 props,这一点很重要;如果 React 需要将自定义 props 映射到 DOM 元素上的实际 props,那么我们需要重新渲染每个新的动画值,这是次优的。尽管如此,当我们不启用自定义组件来获取引用时,这正是发生的情况。因此,即使我们继续使用自定义 propsbackgroundColor而不是style. 然而,版本号 6 不起作用,属性backgroundColor, 只会被添加到设置了 ref 的 DOM 元素中,这不会导致任何更改,因为该属性不是本机divDOM 元素上的属性。
我已经创建了两个可用的沙箱:
第一个沙箱显示了此答案中的组件。每个组件在渲染时都会在控制台中打印输出。检查这些打印输出并验证上述行为。沙盒
第二个沙盒具有相同的主题,但稍微高级一些。这里渲染的数量保持最新,以便我们可以验证不同的行为。该沙箱的要点之一是,我们使用 API 所做的所有动画更改都是“免费”的,因为它不会增加受影响组件的渲染数量。当父级重新渲染时,像往常一样,所有子级也会重新渲染。底部添加了重要要点列表。沙盒
react-spring。animated.react-spring到一个元素。react-spring使用引用进行更新,并且由于您无法同时将引用附加到多个元素(也不forwardRef提供添加多个引用的方法),因此您无法在不重新渲染的情况下在包装组件中的多个位置使用弹簧否则您将无法达到预期的功能。对于这样的组件,最好SpringValue<T>作为 props 传递并animated在组件内部使用原生元素。不:
const NestedComp = ({ style1, style2 }) => {
...
return (
<div style={ style1 }>
<div style={ style2 }>
....
</div>
</div>
)
}
const Wrapped = animated(NestedComp)
Run Code Online (Sandbox Code Playgroud)
做:
const NestedComp = ({ springStyle1, springStyle2 }) => {
...
return (
<animated.div style={ springStyle1 }>
<animated.div style={ springStyle2 }>
....
</animated.div>
</animated.div>
)
}
Run Code Online (Sandbox Code Playgroud)
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 } />
}
Run Code Online (Sandbox Code Playgroud)
做:
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 }}
/>
)
}
Run Code Online (Sandbox Code Playgroud)
在上面,我们希望Comp在不使用 React 的情况下进行更新,但 React 需要组装正确的styleprop,因为color它不是div元素的原生 prop,因此通过 ref 更新color只会导致在元素color上设置 prop ,div而不会执行任何操作。另一方面,当我们添加style父级中已有的属性时,animated组件将检测到其中一个styleprops 是 aSpringValue并相应地更新它将达到预期的效果。
最后,请记住,如果我们没有使用 api 更新自定义组件,我们可以简单地避免设计自定义组件,以便它可以采用 ref 并使用我们想要的任何 prop 名称;react-spring无论如何,现在都会在每个动画帧上重新渲染组件,因此 React 会将所有自定义道具映射到正确的本机 DOM 元素道具。尽管如此,这种执行策略并不可取。
只是为了开始谈话。
让我们从文档的示例开始。假设您有一个第三方 Donut 组件。它具有百分比属性。并且您想要根据此属性制作动画。因此,您可以使用动画作为 Donut 的包装。
const AnimatedDonut = animated(Donut)
// ...
const props = useSpring({ value: 100, from: { value: 0 } })
return <AnimatedDonut percent={props.value} />
Run Code Online (Sandbox Code Playgroud)
是不是哪里出现了问题?