代码之家  ›  专栏  ›  技术社区  ›  Ilja

使用uselayouteffect响应页面/布局转换

  •  2
  • Ilja  · 技术社区  · 6 年前

    我定义了以下结构,以便根据路径名动画布局之间的转换。

    <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 */
    

    一般来说,这个概念是可行的,也就是说,我在制作动画时看到我的“当前”孩子。 外面的 然后作为 activeChildren 设置为空,我在动画时看到我的“新”孩子 在里面 .

    唯一的问题是好像我什么时候开始 setActiveChildren(children); 布局重新呈现,我看到闪烁,布局显示的页面返回到初始状态。

    有没有一种方法可以避免这种情况,当我们在外面制作动画时,会冻结孩子,这样就不会在他们身上重新渲染了?

    编辑:来自react本机项目的完整代码段

    核心思想是我们订阅路由器上下文,当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;
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   Ryan Cogswell    6 年前

    首先,我将通过列出当用户从“/registration”开始,然后切换到“/dashboard”时将发生的步骤来描述我认为当前代码正在发生的情况:

    • 初始呈现 AnimRouteLayout 具有 rootPathname='/registration'
      • 初始布局效果已排队
      • activeChildren 是未定义的,所以 children 返回以呈现
      • <RegistrationLayout /> 被渲染
      • 执行排队布局效果
        • prevRootPathname.current 未定义,因此没有动画
        • 布局效果清理已注册为react
    • 用户切换到“/dashboard”触发呈现 动画程序输出 具有 rootPathname='/dashboard'
      • 自从 rootPathname 不同,第二个布局效果排队
      • 活跃儿童 仍然未定义,因此 儿童 返回以呈现
      • <注册布局/> 卸载和 <DashboardLayout /> 被渲染
      • 执行上一布局效果的清理
        • prevrootpathname.current当前 设置为“/registration”
        • 活跃儿童 设置为前一个子级,导致另一个呈现排队
      • 排队布局效果执行并启动动画
      • 的另一个呈现 动画程序输出 开始由于 活跃儿童 状态变化
      • 另一种布局效果是 排队因为 根路径名 没有什么不同
      • 活跃儿童 返回以呈现
      • <仪表板布局/> 卸载
      • <注册布局/> 以新状态重新执行并呈现
      • 动画完成并设置 活跃儿童 返回未定义
      • 动画程序输出 再次渲染,这次 <仪表板布局/> 将呈现

    尽管有可能 活跃儿童 以一种防止重新安装的方式,我认为有一种更清洁的方法来解决这个问题。而不是试图冻结 儿童 我想你最好把 路径名 . 我在写作时对这些想法做了大量的实验 this answer . 我提出的保持这一点的术语是区分:

    • targetPathname 用户指示要使用的路径
    • renderPathname 当前正在呈现的路径

    大多数时候,这些路径都是相同的。异常发生在退出转换期间,当 渲染路径名 将具有上一个的值 目标路径名 . 通过这种方法,您可以获得如下内容:

    <AnimRouteLayout targetPathname={pathname}>
      {(renderPathname)=> {
        return <>
          {renderPathname.includes('/registration') && <RegistrationLayout />}
          {renderPathname.includes('/dashboard') && <DashboardLayout />}
        </>;
      }}
    </AnimRouteLayout>
    

    然后 动画程序输出 只是需要管理 渲染路径名 适当地:

    function AnimRouteLayout({ children, targetPathname }) {
      const [renderPathname, setRenderPathname] = useState(targetPathname);
      // End of animation would set renderPathname to targetPathname
      return children(renderPathname);
    }
    

    因为我没有试图给出一个有效的例子,所以我不能保证上面没有语法错误,但是我很有信心这个方法是合理的。

    推荐文章