繁体   English   中英

react-spring 如何使用动画(组件)

[英]react-spring how to use animated(Component)

我正在尝试解决图像 slider 中的一些性能问题,我发现使用animated.img产生的性能比使用带有一些反应组件的animated.div产生的性能要好得多。

react 组件显然不仅仅是为了好玩而放置在里面,但幸运的是 react-spring 让您可以通过以下方式为自定义组件设置动画

const AnimatedComponent = animated(Component)

根据文档

但是我该如何使用它呢? 我一直在尝试,但 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)

我剥离了一些第一个道具,因为我从我试图制作动画的组件中认出了它们并且我知道它们存在。

有人试过用这个吗? 如何使用它的示例将非常好。

如果这很重要,我正在使用 react-spring 的9.0.0-rc.3版本。

背景

react-spring不像 css 过渡 api 那样依赖于时间,而是从物理上下文中进行过渡和动画。 为了在 React 中以可接受的性能实现这一点,它绕过了 React 并对相关的 DOM 节点本身进行了修改。

常规react-spring animation 组件

正如您可能已经看到的,所有常规 DOM 节点都以react-spring等效项的形式存在。 例如animation.spananimation.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基本用法

useSpringreact-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 函数,这样我们就不必在每次颜色更改时重新渲染组件。 需要明确的是,当您使用此方法时,您不会更改传递给您想要动画的组件的任何道具,因此您可以通过 api 更改其 state 而无需重新渲染它,只要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元素上可用的道具,我们的自定义组件在其上附加了转发的 ref。 更多关于这进一步下降。

上面的组件是幼稚的,因为animated包装器不能在不重新渲染的情况下更新被包装的组件。 为什么? 仅仅因为我们包装的组件不接受 ref 并且react-spring要求能够在不重新渲染的情况下更新我们的组件是不合理的。

6.用一个适当的自定义组件包裹在animated

让我们通过让它接受一个 ref 来增强我们的包装组件。

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 来 map 自定义道具到 DOM 元素上的实际道具,那么我们需要在每个新的 animation 值上重新渲染,这是次优的。 尽管如此,当我们不启用我们的自定义组件来获取引用时,这正是发生的情况。 因此,即使我们继续使用自定义道具backgroundColor而不是style ,版本 5 也可以工作。 然而,版本号 6 不起作用,并且属性backgroundColor将简单地添加到设置了 ref 的 DOM 元素中,这不会导致任何更改,因为该属性不是本机div DOM 元素上的属性。

沙盒

我提供了两个沙箱:

  • 第一个沙箱显示了此答案中的组件。 每个组件在渲染时都会在控制台中打印输出。 检查这些打印输出并验证上述行为。沙盒

  • 第二个沙盒主题相同,但稍微高级一些。 这里渲染的数量保持最新,以便我们可以验证不同的行为。 这个沙盒的一个收获是,我们使用 api 所做的所有动画更改都是“免费的”,因为它不会增加受影响组件的渲染数量。 当父级像往常一样重新渲染时,所有子级也会重新渲染。 底部添加了重要点列表。 沙盒

结论

  • 使用react-spring挂钩时,请始终使用 api 方法。
  • 总是让你的组件在用animated包装时接受一个 ref。
  • 如果您希望能够在不重新渲染的情况下更新包装的组件,则只能将react-spring的行为附加到 ONE 元素。 react-spring使用 ref 进行更新,并且由于您不能同时将 ref 附加到多个元素( forwardRef也不提供添加多个 ref 的方法),因此您不能在多个地方使用 spring包装组件而不重新渲染它,否则您将无法达到预期的功能。 对于这样的组件,最好将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, height: "100px" }} />
    )
}

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 需要组装正确的style属性,因为color不是div元素的自然属性,因此通过 ref 更新color只会导致设置color属性在什么都不做的div元素上。 另一方面,当我们在父级中添加style属性时, animated组件会检测到其中一个style道具是SpringValue并相应地更新它会产生预期的效果。

最后,请记住,如果我们没有在使用 api 更新我们的自定义组件之后,我们可以简单地避免设计我们的自定义组件,以便它可以获取 ref 并使用我们想要的任何道具名称; 无论如何, react-spring现在将在每个 animation 帧上重新渲染组件,因此 React 会将 map 所有自定义道具转换为正确的原生 DOM 元素道具。 尽管如此,这种执行策略还是不可取的。

只是为了开始谈话。

让我们从文档的示例开始。 假设您有一个第三方甜甜圈组件。 它具有百分比属性。 并且您想根据此属性制作动画。 因此,您可以使用动画作为 Donut 的包装器。

const AnimatedDonut = animated(Donut)
// ...
const props = useSpring({ value: 100, from: { value: 0 } })
return <AnimatedDonut percent={props.value} />

问题出在哪里?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM