官方文檔 👉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 屬性吧。