【发布时间】:2019-02-03 12:12:26
【问题描述】:
我定义了以下结构,以便根据路径名在布局之间进行动画转换。
<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 时,我在动画 in。
唯一的问题是,似乎当我设置 setActiveChildren(children); 布局重新渲染时,我看到闪烁并且布局显示的页面返回到其初始状态。
有没有办法在我们制作动画时避免这种情况以及冻结孩子,因此不会发生重新渲染?
编辑:来自 react-native 项目的完整代码 sn-p
核心思想是我们订阅路由器上下文,当 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;
【问题讨论】:
-
还有其他地方叫
setActiveChildren吗?查看useState调用(我假设setActiveChildren是useState的setter)以及处理该状态的任何行的确切代码将有所帮助。有些东西导致孩子们重新安装,但如果没有看到更多细节,很难确定原因。 -
@RyanCogswell我试图让我的问题保持简单,因为它基于 react-native 组件。但同样的逻辑也适用。我在问题中添加了完整的代码剪辑器并进行了一些解释。现在我需要想办法让
activeChildren成为一种不会根据路由器上下文变化而变化的子节点的冻结快照。
标签: reactjs react-native react-hooks