官方文档 👉react-transition-group 文档react
在 react 项目中可以使用官网提供的动画过渡库 react-transition-group 来实现切换页面(路由切换)时的过渡效果。git
react-transition-group 中,暴露了三个组件,分别是:github
Transition
CSSTransition
TransitionGroup
其中最重要的是 CSSTransition
,而 TransitionGroup
用于列表项的过渡动画。项目中我也是使用了这两个组件。typescript
TransitionGroup
不提供任何形式的动画,具体的动画取决与咱们包裹的 Transition
|| CSSTransition
的动画,因此咱们能够在列表里面作出不一样类型的动画。编程
CSSTransition
组件中较为重要的 api 有:api
in:boolean,控制组件显示与隐藏,true
显示,false
隐藏。浏览器
timeout:number,延迟,涉及到动画状态的持续时间。也可传入一个对象,如{ exit:300, enter:500 }
来分别设置进入和离开的延时。bash
classNames:string,动画进行时给元素添加的类名。通常利用这个属性来设计动画。这里要特别注意是 classNames 而不是className。react-router
unmountOnExit:boolean,为 true
时组件将移除处于隐藏状态的元素,为 false
时组件保持动画结束时的状态而不移除元素。通常要设成 true
。app
appear:boolean,为 false
时当 CSSTransition
控件加载完毕后不执行动画,为 true
时控件加载完毕则当即执行动画。若是要组件初次渲染就有动画,则须要设成 true
。
key:string,这个属性是配合 TransitionGroup
组件来使用的,能够经过key来判断是否须要触发动画。这个属性十分重要!
classNames属性的做用是:当组件被应用动画时,不一样的动画状态(enter,exits,done)将做为className属性的后缀来拼接为新的className,如为 CSSTransition
组件设置了如下属性:
<CSSTransition
classNames={'fade'}
appear={true}
key={location.pathname}
timeout={300}
unmountOnExit={true}
>
/* 省略... */
</CSSTransition>
复制代码
fade-enter
、fade-enter-active
、fade-enter-done
、fade-exit
、fade-exite-active
、fade-exit-done
、fade-appear
以及 fade-appear-active
多个className。每个独立的className都对应着单独的状态。关于 react-router 的基本知识可具体查看官方文档 👉react-router文档,这里就再也不重复进行介绍。
这里介绍你们平时没注意的关于 Switch
组件的冷知识,也是实现路由切换动画的关键!
Switch
有一个很重要的属性:location。通常咱们不会给该组件设置 location 属性。有无该属性的区别:
Switch
组件的子组件(通常是 Route 或 Redirect)会根据当前浏览器的 location 做为匹配依据來进行路由匹配。Switch
组件的子组件会根据定义的 location 做为匹配依据。看完基本介绍,下面就看看如何在项目中使用 react-transition-group 实现页面切换过渡效果吧。
yarn add react-transition-group
yarn add @types/react-transition-group --dev
复制代码
import { createHashHistory } from 'history';
import React from 'react';
import { Router } from 'react-router';
import { Route, Switch, withRouter } from 'react-router-dom';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import routeData from './common/route'; // 路由配置
import NotFound from './views/Exception';
const history = createHashHistory();
const Routes = withRouter(({ location }) => (
<TransitionGroup className={'router-wrapper'}>
<CSSTransition timeout={300} classNames={'fade'} key={location.pathname} unmountOnExit={true}>
<Switch>
{routeData.map(({ path, component, exact }: IRouterItem) => (
<Route key={path} path={path} component={component} exact={exact} />
))}
<Route component={NotFound} />
</Switch>
</CSSTransition>
</TransitionGroup>
));
const App: React.FC = () => {
return (
<Router history={history}>
<Routes />
</Router>
);
};
export default App;
复制代码
默认状况下必须是通过路由匹配渲染的组件才存在 this.props,才拥有路由参数,才能使用编程式导航的写法。然而不是全部组件都直接与路由相连(经过路由跳转到此组件)的,当这些组件须要路由参数时,使用 withRouter 就能够给此组件传入路由参数,此时就可使用 this.props。
好比 App.js 这个组件,通常是首页,不是经过路由跳转过来的,而是直接从浏览器中输入地址打开的,若是不使用 withRouter,此组件的 this.props 为空,无法执行 props 中的 history、location 和 match 等方法。
为了让入口文件 App.tsx 看起来更加简洁,我将使用了 react-transition-group 的路由切换相关代码封装成Routes
组件。
修改后的入口文件 App.tsx 内容以下:
import { createHashHistory } from 'history';
import React from 'react';
import { Router } from 'react-router';
import Routes from './components/Routes';
const history = createHashHistory();
const App: React.FC = () => {
return (
<Router history={history}>
<Routes />
</Router>
);
};
export default App;
复制代码
Routes
组件内容以下:import React from 'react';
import { Route, Switch, withRouter } from 'react-router-dom';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import routeData from '../../common/route';
import NotFound from '../../views/Exception';
interface IRouterItem {
component?: React.ComponentType;
path?: string;
exact?: boolean;
}
class Routes extends React.Component<any> {
render () {
const { location } = this.props;
return (
<TransitionGroup className={'router-wrapper'}>
<CSSTransition
classNames={'fade'}
appear={true}
key={location.pathname}
timeout={300}
unmountOnExit={true}
>
<Switch location={location}>
{routeData.map(({ path, component, exact }: IRouterItem) => (
<Route key={path} path={path} component={component} exact={exact} />
))}
<Route component={NotFound} />
</Switch>
</CSSTransition>
</TransitionGroup>
);
}
}
export default withRouter(Routes);
复制代码
/* 动画相关样式 */
.fade-enter, .fade-appear {
opacity: 0;
}
.fade-enter.fade-enter-active, .fade-appear.fade-appear-active {
opacity: 1;
transition: opacity 300ms ease-in;
}
.fade-exit {
opacity: 1;
}
.fade-exit.fade-exit-active {
opacity: 0;
}
复制代码
import './index.less';
复制代码
⚠ ️若是只是想实现过渡效果,按照上面介绍的内容便可实现
⚠ ️若是想了解出现以上两种bug的缘由,则能够继续看下面的内容。
react-loadable
的第三方库来进行代码拆分,实现组件按需加载。(相关介绍可具体查看前一篇博客 👉 react + typescript 项目的定制化过程进行了解)react-loadable
后首次加载时页面切换没有过渡效果,具体看下面的效果:
react-loadable
进行组件按需加载了😭。// path:src/common/route.tsx
import * as React from 'react';
import DetailPage from '../views/DetailPage';
import Exception from '../views/Exception';
import HomePage from '../views/HomePage';
const routeConfig: any = [
{
path: '/',
component: HomePage,
},
{
path: '/detail/:id',
component: DetailPage,
},
/**
* Exception 页面
*/
{
path: '/exception/404',
component: Exception,
},
];
function generateRouteConfig (route: IRouteConfig[]) {
return route.map(item => {
return {
key: item.path,
exact: typeof item.exact === 'undefined' ? true : item.exact,
...item,
component: item.component,
};
});
}
export default generateRouteConfig(routeConfig);
复制代码
componentDidMount
方法中调接口(请求相关数据)。componentDidMount
方法会在render()以后当即执行,拉取数据后使用setState()
方法触发从新渲染(re-render)。Routes
组件内容以下:import React from 'react';
import { Route, Switch, withRouter } from 'react-router-dom';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import routeData from '../../common/route';
import NotFound from '../../views/Exception';
interface IRouterItem {
component?: React.ComponentType;
path?: string;
exact?: boolean;
}
class Routes extends React.Component<any> {
render () {
const { location } = this.props;
return (
<TransitionGroup className={'router-wrapper'}>
<CSSTransition
classNames={'fade'}
appear={true}
key={location.pathname}
timeout={300}
unmountOnExit={true}
>
<Switch>
{routeData.map(({ path, component, exact }: IRouterItem) => (
<Route key={path} path={path} component={component} exact={exact} />
))}
<Route component={NotFound} />
</Switch>
</CSSTransition>
</TransitionGroup>
);
}
}
export default withRouter(Routes);
复制代码
Switch
组件设置location属性。致使首次加载后进入详情页,切出来都会请求两次接口,具体看看下面的演示:
Switch
有一个很重要的属性:location。通常咱们不会给该组件设置 location 属性。有无该属性的区别:
- 不设置location属性:
Switch
组件的子组件(通常是 Route 或 Redirect)会根据当前浏览器的 location 做为匹配依据來进行路由匹配。- 设置location属性:
Switch
组件的子组件会根据定义的 location 做为匹配依据。
CSSTransition
这个组件中的 key 属性是配合 TransitionGroup
组件使用的,能够经过 key 来判断是否须要触发动画。CSSTransition
组件中的 key 属性决定该节点是否显示,而 Router
组件中的 location 属性会在路由发生变化时进行更新,刚好 location 的 pathname 能够做为 CSSTransition
组件中的 key 属性。当路由切换的时候, location 对象就会发生改变,新的 key key会使得页面从新渲染时出现两个 CSSTransition
。CSSTransition
组件配置 key 属性,会发现旧节点会去匹配新的路由内容,这是由于 Route
组件默认根据当前浏览器的 location 进行匹配,为了让旧节点根据旧的 location 进行匹配,则须要设置 Switch
组件的 location 属性。Switch
组件加个 location 属性吧。