Реагируйте на переходы страницы / макета с помощью useLayoutEffect - PullRequest
0 голосов
/ 03 февраля 2019

У меня есть следующая структура, определенная для анимации переходов между макетами на основе имени пути.

<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 установлены в ноль, я вижу своих «новых» детей при анимации в .

Единственная проблема заключается в том, что кажется, что когда я устанавливаю 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;

1 Ответ

0 голосов
/ 03 февраля 2019

Сначала я опишу то, что, по моему мнению, происходит с вашим текущим кодом, изложив шаги, которые будут происходить, когда пользователь начинает с «/ registration», а затем переключается на «/ dashboard»:

  • Первоначальный рендеринг AnimRouteLayout с rootPathname='/registration'
    • Первоначальный эффект макета ставится в очередь
    • activeChildren не определен, поэтому children возвращается для рендеринга
    • <RegistrationLayout /> отображается
    • выполняется эффект макета в очереди
      • prevRootPathname.current не определен, поэтому анимация
      • очистка эффекта макета не зарегистрирована в React
  • Пользователь переключается в режим рендеринга "/ dashboard" AnimRouteLayout с rootPathname='/dashboard'
    • , поскольку rootPathname отличается, второй эффект макета ставится в очередь
    • activeChildren по-прежнему не определено, поэтому children возвращается для визуализации
    • <RegistrationLayout /> демонтируется и <DashboardLayout /> визуализируется
    • выполняется очистка для предыдущего эффекта макета
      • prevRootPathname.current устанавливается на '/ registration'
      • activeChildren устанавливается на tпредыдущие дети, вызывающие в очереди другой рендер
    • , выполняется эффект макета в очереди и запускается анимация
    • начинается еще один рендер AnimRouteLayout из-за состояния activeChildrenизменить
    • дополнительный эффект макета не в очереди, потому что rootPathname не отличается
    • activeChildren возвращается для визуализации
    • <DashboardLayout />unmounts
    • <RegistrationLayout /> перемонтирует со свежим состоянием и визуализируется
    • анимация завершается и устанавливает activeChildren обратно в неопределенное значение
    • AnimRouteLayout визуализируется снова и на этот раз <DashboardLayout />будет отображаться

Хотя было бы возможно управлять activeChildren способом, который предотвращает повторную установку, я думаю, что есть более чистый способ решения этой проблемы.Вместо того, чтобы пытаться заморозить детей , я думаю, вам лучше заморозить pathname .Я много экспериментировал с этими идеями, когда писал этот ответ .Терминология, которую я придумал, чтобы не усложнять это, заключается в том, чтобы различать:

  • 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);
}

Так как я не пыталсяПриведу рабочий пример этого, я не даю никаких гарантий, что у меня нет синтаксической ошибки в вышеприведенном, но я вполне уверен, что подход обоснован.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...