我定义了以下结构,以便根据路径名对布局之间的过渡进行动画处理。
<LayoutTransition pathname={pathname}>
{pathname.includes('/registration') && <RegistrationLayout />}
{pathname.includes('/dashboard') && <DashboardLayout />}
</LayoutTransition>
RegistrationLayout
和DashboardLayout
的内部结构相似,但是它们根据路径名而不是布局显示不同的页面。
在我的LayoutTransition
组件中,我有以下逻辑
useLayoutEffect(() => {
// Root paths are "/registration" "/dashboard"
const rootPathname = pathname.split('/')[1];
const rootPrevPathname = prevPathname.current.split('/')[1];
if (rootPathname !== rootPrevPathname) {
/*
* Logic that:
* 1. Animates component to 0 opacity
* 2. Sets activeChildren to null
* 3. Animates component to 1 opacity
*/
}
return () => {
// Sets activeChildren to current one
setActiveChildren(children);
};
}, [pathname]);
/* renders activeChildren || children */
通常,此概念有效,即在动画 out 时看到我的“当前”子代,然后将activeChildren
设置为null,在动画时看到我的“新”子代。在。
唯一的问题是,当我设置setActiveChildren(children);
布局重新呈现时,似乎看到了闪烁,并且布局显示的页面返回到其初始状态。
当我们制作动画时,有没有办法避免这种情况并冻结孩子,所以不会对它们进行重新渲染?
编辑:本机项目的完整代码段
核心思想是我们订阅路由器上下文,当rootPathname更改时,我们将当前布局(子级)设置为动画,然后将新的布局设置为动画。
import React, { useContext, useLayoutEffect, useRef, useState } from 'react';
import { Animated } from 'react-native';
import RouterCtx from '../context/RouterCtx';
import useAnimated from '../hooks/useAnimated';
import { durationSlow, easeInQuad } from '../services/Animation';
/**
* Types
*/
interface IProps {
children: React.ReactNode;
}
/**
* Component
*/
function AnimRouteLayout({ children }: IProps) {
const { routerState } = useContext(RouterCtx);
const { rootPathname } = routerState;
const [activeChildren, setActiveChildren] = useState<React.ReactNode>(undefined);
const [pointerEvents, setPointerEvents] = useState(true);
const prevRootPathname = useRef<string | undefined>(undefined);
const [animatedValue, startAnimation] = useAnimated(1, {
duration: durationSlow,
easing: easeInQuad,
useNativeDriver: true
});
function animationLogic(finished: boolean, value: number) {
setPointerEvents(false);
if (finished) {
if (value === 0) {
setActiveChildren(undefined);
startAnimation(1, animationLogic, { delay: 150 });
}
setPointerEvents(true);
}
}
useLayoutEffect(() => {
if (prevRootPathname.current) {
if (rootPathname !== prevRootPathname.current) {
startAnimation(0, animationLogic);
}
}
return () => {
prevRootPathname.current = rootPathname;
setActiveChildren(children);
};
}, [rootPathname]);
return (
<Animated.View
pointerEvents={pointerEvents ? 'auto' : 'none'}
style={{
flex: 1,
opacity: animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0, 1] }),
transform: [
{
scale: animatedValue.interpolate({ inputRange: [0, 1], outputRange: [1.1, 1] })
}
]
}}
>
{activeChildren || children}
</Animated.View>
);
}
export default AnimRouteLayout;
答案 0 :(得分:1)
首先,我将介绍当用户从“ / registration”开始然后切换到“ / dashboard”时发生的步骤,以描述我当前代码所发生的情况:
AnimRouteLayout
对rootPathname='/registration'
的初始呈现
activeChildren
未定义,因此children
返回被渲染<RegistrationLayout />
已呈现prevRootPathname.current
未定义,因此没有动画AnimRouteLayout
触发rootPathname='/dashboard'
的呈现
rootPathname
不同,因此第二种布局效果已排队activeChildren
仍未定义,因此children
返回被渲染<RegistrationLayout />
卸载并渲染<DashboardLayout />
prevRootPathname.current
设置为'/ registration'activeChildren
设置为先前的子项,导致另一个渲染排队等候AnimRouteLayout
的变化,activeChildren
的另一次渲染开始了rootPathname
没什么不同,没有排队的另一种布局效果activeChildren
返回待渲染<DashboardLayout />
卸载<RegistrationLayout />
以新状态重新安装并呈现activeChildren
设置为未定义AnimRouteLayout
再次渲染,这次<DashboardLayout />
将被渲染尽管可以通过防止重新安装的方式来管理activeChildren
,但我认为有一种更干净的方法可以解决此问题。我认为您最好冻结路径名,而不是冻结孩子。在编写this answer时,我对这些想法做了很多实验。为了弄清楚这一点,我想出了一个术语来区分:
targetPathname
用户指示他们要使用的路径renderPathname
当前正在渲染的路径大多数情况下,这些路径是相同的。唯一的例外是在退出过渡期间,renderPathname
将具有前一个targetPathname
的值。使用这种方法,您将获得以下内容:
<AnimRouteLayout targetPathname={pathname}>
{(renderPathname)=> {
return <>
{renderPathname.includes('/registration') && <RegistrationLayout />}
{renderPathname.includes('/dashboard') && <DashboardLayout />}
</>;
}}
</AnimRouteLayout>
然后AnimRouteLayout
仅需要适当地管理renderPathname
:
function AnimRouteLayout({ children, targetPathname }) {
const [renderPathname, setRenderPathname] = useState(targetPathname);
// End of animation would set renderPathname to targetPathname
return children(renderPathname);
}
由于我没有尝试给出一个可行的示例,因此我无法保证上面没有语法错误,但是我相当有信心这种方法是正确的。