react-router4路由切换动画

写在前面

之前我写过关于react-transition-groupreact-motion的使用教程,就控制粒度上来说,react-motion要好很多,但是react-motion有个比较麻烦的问题,就是我暂时没找到开箱即用的动画封装,而用react-motion也没法使用animate.css,所以如果项目仅仅只用于web端,没考虑native,那么建议还是使用react-transition-group,会方便很多

用到的第三方库

  • react-router-dom 4.2.2 用于路由
  • react-transition-group 2.2.1 用于react动画实现,这里需要注意下,使用的不是版本1,而是只包含{Transition, TransitionGroup, CSSTransition}的版本2
  • animate.css 用于动画效果

热身

在正式开始写路由的切换动画前,我们先用react-transition-group结合animate.css来实现一个简单的组件进出场动画,以此回顾之前关于react-transition-group的知识
react-transition-group文档

    <div>
        <button onClick={this.toggleState}>click</button>
        {/*第一个例子*/}
        <CSSTransition
          in={this.state.show}
          classNames={{
            enter: 'animated',
            enterActive: 'bounceIn',
            exit: 'animated',
            exitActive: 'bounceOut'
          }}
          timeout={500}
          mountOnEnter={true}
          unmountOnExit={true}
        >
          <div className="box" />
        </CSSTransition>
      </div>

效果

demo1.gif

代码很简单,用in控制组件的显示和隐藏,用classNames控制组件进出场的className,稍微需要注意的是与animate.css的结合方式

路由切换

回顾了组件的进场和出场动画实现后,我们正式来开始写路由的切换动画。先理清楚思路,在react-router4里面,每个路由对应其实就是一个组件,无非就是在路由匹配到的时候,将CSSTransitionin设置为true,不匹配设置为false,仅此而已。
唯一麻烦的地方在于怎么获取路由的匹配信息,翻看react-router4的api,我们看到,Route和渲染有关的props有三个,component,render,children,componentrender都拿不到匹配信息,只要路由匹配到,组件就会mount,反之,就会unmount,我们无法进行控制,而children正好符合我们的期望,它与render类似,不同的地方在于无论path是否匹配,都会被触发,然后会将当前的match信息传递过来,我们也正好可以通过match来控制CSSTransition

先写一个无动画的路由切换

不管怎么样,我们先写个简单的路由切换,然后再对其进行改装

主路由

<Router>
        <div className="router4-transition">
          <div className="nav">
            <NavLink to="/" exact className="nav-item" activeClassName="active">
              home
            </NavLink>
            <NavLink to="/page1" className="nav-item" activeClassName="active">
              page1
            </NavLink>
            <NavLink to="/page2" className="nav-item" activeClassName="active">
              page2
            </NavLink>
          </div>

          <div className="pages">
            <Route
              path="/"
              exact
              component={props => {
                if(!props.match) return null
                return <Index />
              }}
            />
            <Route
              path="/page1"
              children={props => {
                if(!props.match) return null
                return <Page1 />
              }}
            />
            <Route
              path="/page2"
              children={props => {
                if(!props.match) return null
                return <Page2 />
              }}
            />
          </div>
        </div>
      </Router>

Index

class Index extends Component {
    render() {
      return <div className="page index">index</div>
    }
  }

Page1

class Page1 extends Component {
    render() {
      return <div className="page page1">page1</div>
    }
  }

Page2

class Page2 extends Component {
    render() {
      return <div className="page page2">page2</div>
    }
  }

简单的路由就写好了,接下来考虑加动画

利用高阶组件给页面加上动画

我并不希望在页面内部实现动画逻辑,首先是页面逻辑与动画逻辑无关,其次如果每写一个页面就写一次动画逻辑,我怕是要累死,所以我们这里将动画逻辑单独抽取出来,封装成一个高阶组件

function wrapAnimation(WrappedComponent) {
  return class extends Component {
    render() {
      return (
        <CSSTransition
          in={this.props.match !== null}
          classNames={{
            enter: 'animated',
            enterActive: 'fadeInDown',
            exit: 'animated',
            exitActive: 'fadeOutDown'
          }}
          timeout={1000}
          mountOnEnter={true}
          unmountOnExit={true}
        >
          <WrappedComponent {...this.props} />
        </CSSTransition>
      )
    }
  }
}

也是没啥可讲的代码,接下来,我们将我们的页面Index, Page1, Page2外面套一层高阶组件

const Index = wrapAnimation(
  class Index extends Component {
    render() {
      return <div className="page index">index</div>
    }
  }
)

const Page1 = wrapAnimation(
  class Page1 extends Component {
    render() {
      return <div className="page page1">page1</div>
    }
  }
)

const Page2 = wrapAnimation(
  class Page2 extends Component {
    render() {
      return <div className="page page2">page2</div>
    }
  }
)

ok,然后再稍微修改下我们的路由层

<Router>
        <div className="router4-transition">
          <div className="nav">
            <NavLink to="/" exact className="nav-item" activeClassName="active">
              home
            </NavLink>
            <NavLink to="/page1" className="nav-item" activeClassName="active">
              page1
            </NavLink>
            <NavLink to="/page2" className="nav-item" activeClassName="active">
              page2
            </NavLink>
          </div>

          <div className="pages">
            <Route
              path="/"
              exact
              children={props => {
                return <Index {...props} />
              }}
            />
            <Route
              path="/page1"
              children={props => {
                return <Page1 {...props} />
              }}
            />
            <Route
              path="/page2"
              children={props => {
                return <Page2 {...props} />
              }}
            />
          </div>
        </div>
      </Router>

接下来,直接看效果吧


router4-transition.gif

总结

和普通的组件切换动画差不多,只是需要注意下怎么在react-router4的路由中获取组件的显示状态

完整的代码我放到了github上: https://github.com/soyal/router4-transition

感谢你的阅读

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,982评论 25 708
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,198评论 4 61
  • Serialization解析 添加如下引用 序列化 反序列化 Newtonsoft解析 添加引用通过NuGet引...
    ChainZhang阅读 4,712评论 0 4
  • 1、美观大方、可嵌入或摆放桌面使用 2、支持快充、充电快、不发烫、声音提示、安全可靠 3、集成过压、过流、过温保护...
    手机车载无线充电器阅读 304评论 0 0
  • “爸爸,我今天数学考试得了第一名!”儿子兴冲冲的推开门,人还没到家,话已传出很远了。 儿子也不等爸爸回答,又接着说...
    萍易近人阅读 380评论 0 0