簡體   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