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/
- Документация: http://yeutech-lab.github.io/react-router-dom-utils
- Источники: http://github.com/yeutech-lab/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,
});
},
},
],
});