【问题标题】:React Router + Page animations + ModularityReact Router + 页面动画 + 模块化
【发布时间】:2019-01-20 21:01:13
【问题描述】:

这是一个关于 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 从下往上挂载,从下往下滑卸载。

我们将面临两个问题:如果我们使用&lt;CSSTransition&gt; 组件,我们将不得不根据渲染的页面向它传递不同的classNames 属性。我还没有用这个策略实现任何东西,因为还有一个问题让我担心:如果按照这个路径,我们会将用于动画页面组件的逻辑存储在&lt;CSSTransition&gt;组件中,而这是在页面之外。这在我看来并不是非常模块化和孤立的,而且我阅读的关于软件设计、模块化和孤立的书籍越多,我就越对这种方法感到困惑。

在理想情况下,页面动画的逻辑应该在每个页面的主要组件中定义。比如:

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-router 不知道我们的转换,它会立即卸载页面以进行动画处理,甚至无需等待动画开始——当然,完成——强>。这就是&lt;CSSTransition&gt; 组件所做的,但将页面转换的逻辑集中在页面组件之外。

因此,考虑到多个页面在装载/卸载时具有不同动画的情况,这是我的问题:

  • A) 之间的转换逻辑是否有任何原因? 页面不应该存储在每个页面的主要组件中吗?

  • B) React-router 被设计为卸载有什么原因吗 组件忽略等待不同类型事件的情况 在不同的页面?

  • C) 有没有我没有考虑的通用方法来解决这个问题?

  • D) 从头开始​​编写自定义 React 路由器是一种选择吗?

尼克。

【问题讨论】:

    标签: reactjs react-router reactcsstransitiongroup


    【解决方案1】:

    您可以在此处使用 Link 组件来管理生命周期:

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

    安装:

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

    该组件支持 React 路由器的生命周期功能,因此您可以处理您的转换逻辑。它不依赖任何额外的库。

    还提供了使用 react-loadable 和鼠标悬停预加载页面块的便捷功能。

    您甚至可以在浏览器空闲时使用 webpack 使用预加载块。

    支持页面转换,您甚至可以控制页面转换的导航顶部加载指示器。

    我遇到了同样的问题,需要更大的灵活性,所以我这样做了。

    编辑

    关于你的 4 个问题:

    A) 为什么页面之间的转换逻辑不应该存储在每个页面的主要组件中?

    是的,当 react-router 中发生页面更改时,它会卸载当前页面并呈现下一个页面。

    如果你想要一个过渡,无论如何,你之前的页面已经被卸载了。

    B) React-router 被设计为卸载组件而忽略在不同页面中等待不同类型事件的情况有什么原因吗?

    是的,因为 react-router 在这里提供了一个逻辑来玩位置和历史来创建 web 应用程序。

    做得很好。

    C) 有没有我没有考虑的通用方法来解决这个问题?

    是的,您必须使用 withRouter 并使用此 API 管理转换。

    D) 从头开始​​编写自定义反应路由器是一种选择吗?

    如果您认为 react-router 对您的情况没有帮助,这听起来是个好主意。完成后请在 GitHub 上分享您的工作。

    最好的

    【讨论】:

    • 感谢@DimitriKopriwa,您的组件看起来很有趣,我会看看。但是你没有解决我的四个问题!我很确定你对此有所了解……;)
    • 谢谢@DimitriKopriwa! A)是的,但是为什么 react-router 没有被设计为监听将卸载的组件内的事件? B) 是的,确实如此……但是对于不适合普通简单案例的开发人员来说,缺乏监听逻辑正在使他们发疯。 C)withRouter…我会深入研究它:)
    • A) 他们不需要。这就是withRouter 在这里的原因。 B)你不必使用他们的链接组件,使用withRouter。 C)你在正确的轨道上。这是有效的答案吗?
    【解决方案2】:

    react-route-transition 是我制作的一个简单的库,可以在使用 react-router 时帮助实现过渡动画。

    只需使用提供的 Provider 包装您的应用,并在组件中使用 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,
            });
          },
        },
      ],
    });
    

    【讨论】:

      猜你喜欢
      • 2023-03-25
      • 2018-02-06
      • 1970-01-01
      • 2018-02-09
      • 2021-08-16
      • 1970-01-01
      • 2022-01-09
      • 1970-01-01
      • 2020-07-09
      相关资源
      最近更新 更多