React Router + анимация страницы + модульность

Это вопрос о маршрутизаторе React и его дизайне, особенно при рассмотрении переходов страниц.

React разработан для модульности, и он превосходен в этой области. Но я нашел одну задачу, которая кажется довольно трудной для выполнения при сохранении изоляции и модульности между компонентами: переходы страниц.

Я прочитал много статей и вопросов, связанных с этим, как в Medium, так и в StackOverFlow среди других сайтов; но до настоящего времени я не читал ни одного описания хорошего архитектурного подхода для решения проблем, которые я здесь рассмотрю, а также объяснения того, почему маршрутизатор React не рассматривает эти распространенные случаи.


Итак, давайте рассмотрим сайт с тремя страницами, PageA, PageB и PageC:

Если мы хотим использовать одну и ту же анимацию для монтирования / размонтирования этих трех страниц, скажем, FadeIn для монтирования и FadeOut для размонтирования, то мы можем использовать компонент React Transition Group для обертывания нашего <Switch> компонент, и он будет отвечать за анимацию страниц с помощью CSS-переходов. Здесь CSS переходы будут контролироваться <CSSTransition> компонент с классом, переданным в ClassNames,

<div className="Main">
    <TransitionGroup component={null}>
        <CSSTransition
            key={locationKey}
            appear={true}
            classNames="Main"
            timeout={{
                enter: 300,
                exit: 300,
            }}
        >
            <Switch location={this.props.location}>
                <Route
                    path="/PageA"
                    render={(props) => {
                        return <PageA />;
                    }}
                />
                <Route
                    path="/PageB"
                    render={(props) => {
                        return <PageA />;
                    }}
                />
                <Route
                    path="/PageC"
                    render={(props) => {
                        return <PageA />;
                    }}
                />
            </Switch>
        </CSSTransition>
    </TransitionGroup>
</div>

Все в порядке, мы счастливы, и на нашем сайте есть отличная анимация, как для монтирования, так и для демонтажа.

Теперь мы хотим сделать разные переходы для каждого действия монтирования / размонтирования для каждой страницы: PageA для монтирования с помощью FadeIn и размонтирования с помощью FadeOut, монтирование PageB с помощью ползунка слева направо и размонтирования, сдвигающегося назад влево, и PageC для монтирования из снизу вверх и размонтирование скольжения назад вниз.

Мы столкнемся с двумя проблемами: если мы будем использовать <CSSTransition> компонент, мы должны будем пройти разные classNames подпирайте его в зависимости от отображаемой страницы. Я еще ничего не реализовал с этой стратегией, потому что есть еще одна проблема, которая касается меня: если мы пойдем по этому пути, мы будем хранить логику для анимации наших компонентов страницы внутри <CSSTransition> компонент, и это за пределами страниц. Это не выглядит очень модульным и изолированным для меня, и чем больше книг я читаю о дизайне программного обеспечения, модульности и изоляции, тем больше я путаюсь с этим подходом.

В идеальном мире логика анимации страниц должна быть определена в главном компоненте каждой страницы. Что-то вроде:

class Home extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            classForAnimation: '',
        };
    }

    componentDidMount() {
        this.setState({
            classForAnimation: ' FadeIn',
        });
    }

    render() {
        return <div className={'Home' + this.state.classForAnimation}>
                   I'm Home page
               </div>;
    }
}

export default Home;

И немного CSS:

.Home {
    opacity: 0;
    transition: opacity 0.5s ease;
}

.FadeIn {
    opacity: 1;
}

Это только для монтажа, действительно легко. Итак, если это лучшее решение, почему мы его не используем?

Причина в том, что React-маршрутизатор не знает о наших переходах и размонтирует страницу для немедленной анимации, даже не дожидаясь запуска анимации и, конечно же, ее завершения. Это то, что <CSSTransition> компонент делает, но концентрирует логику переходов страниц за пределы компонентов страницы.

Итак, учитывая случай нескольких страниц с различными анимациями при монтировании / размонтировании, вот мои вопросы:

  • А) Есть ли причина, по которой логика переходов между страницами не должна храниться внутри основного компонента каждой страницы?

  • Б) Есть ли какая-то причина, по которой React-router были спроектированы для размонтирования компонентов, игнорируя случай ожидания разного типа событий на разных страницах?

  • C) Есть ли общий подход к решению этой проблемы, который я не рассматриваю?

  • D) Написание кастомного реагирующего роутера с нуля было бы вариантом?

Nik.

1 ответ

Здесь вы можете использовать компонент Link, который позволит вам управлять жизненным циклом:

https://yeutech-lab.github.io/react-router-dom-utils/

Монтаж:

npm install @yeutech-lab/react-router-dom-utils --save

Использование:

export default (
  <Link
   component={(props) => <div {...props}>Hello world</div>}
   waitChunk={true}
   // You can pass routes as props if you use waitChunk
   routes={[{ name: 'Home', component: Home, path: '/' }]}
   // OR a ContextConsumer that own it
   ContextConsumer={AppContextConsumer}
   // use lifecycle callbacks
   onClick={actionStartLoadingIndicator}
   onPreload={() => console.log(`
     chunk load in the background on mouseover or
     when user click if waitChunk is true
   `)}
   onLoaded={actionStopLoadingIndicator}
   // use delay function so you can animate what you want
   delay={() => Math.random() * 1000}
  />
);

Этот компонент поддерживает функции жизненного цикла для реагирующего маршрутизатора, поэтому вы можете использовать свою логику перехода. Это не зависит от какой-либо дополнительной библиотеки.

Также доступны удобные функции для предварительной загрузки кусков страниц с помощью реагирующей загрузки и при наведении курсора мыши.

Вы даже можете использовать блок предварительной загрузки, когда браузер не используется с веб-пакетом.

Поддерживается переход на страницы, вы даже можете контролировать индикатор верхней загрузки для перехода на свою страницу.

У меня была та же проблема, и мне нужно было больше гибкости, поэтому я сделал это.

редактировать

О ваших 4 вопросах:

А) Есть ли причина, по которой логика переходов между страницами не должна храниться внутри основного компонента каждой страницы?

Да, когда изменение страницы происходит в реакции-маршрутизаторе, она размонтирует текущую страницу и отображает следующую.

Если вам нужен переход, несмотря ни на что, ваша предыдущая страница уже будет размонтирована.

Б) Есть ли какая-то причина, по которой React-router были спроектированы для размонтирования компонентов, игнорируя случай ожидания разного типа событий на разных страницах?

Да, потому что реагирующий маршрутизатор здесь для обеспечения логики для игры с местоположением и историей для создания веб-приложения.

Это делает это очень хорошо.

C) Есть ли общий подход к решению этой проблемы, который я не рассматриваю?

Да, вы должны использовать withRouter и управлять переходом с помощью этого API.

D) Написание кастомного реагирующего роутера с нуля было бы вариантом?

Это звучит как отличная идея, если вы считаете, что реагирующий маршрутизатор не помогает в вашем случае. Пожалуйста, поделитесь своей работой на GitHub, когда это будет сделано.

Лучший

https://github.com/dutzi/react-route-transition - это простая библиотека, которую я сделал, которая помогает реализовать анимацию перехода при использовании response-router.

Просто оберните свое приложение предоставленным поставщиком и используйте useTransition внутри компонентов, чтобы описать их анимацию входа и выхода.

Например, следующее запустит onEnter анимация при изменении маршрута на "/" (после срабатывания onLeave анимация (ы) предыдущего маршрута) и запустит onLeave анимация при изменении маршрута с "/", прямо перед размонтированием компонента (путем изменения маршрута) и запуском следующего компонента onEnter анимация.

useTransition({
  handlers: [
    {
      path: '/',
      onEnter: async () => {
        await gsap
          .timeline()
          .fromTo(
            '[data-home-main] > *, [data-home-footer]',
            { opacity: 0, y: 20 },
            { duration: 0.6, stagger: 0.125, y: 0, opacity: 1 }
          );
      },
      onLeave: async () => {
        await gsap.timeline().to('[data-home-main] > *, [data-home-footer]', {
          duration: 0.6,
          stagger: 0.125,
          opacity: 0,
          y: -20,
        });
      },
    },
  ],
});
Другие вопросы по тегам