安装React Router
声明路由
创建向导
路由参数
处理not found路由
实现页面重定向
查询参数
路由提示符
内嵌路由
动画转换
lazy loading 路由
安装路由
将React Router添加进项目中,
1 yarn add react-router-dom
以及将TypeScript版本的React Router添加到devDependency的开发依赖中,
1 yarn add -D @types/react-router-dom
声明路由
在页面我们需要使用BrowserRouter
和Route
组件。BrowserRouter
是top-level组件,会寻找下层的Route
组件以决定不同的页面路径。
在引入BrowserRouter
和Route
之前,首先创建两个页面,
创建一个ProductsData.ts
文件,内容如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 export interface IProduct { id : number ; name : string ; description : string ; price : number ; } export const products : IProduct [] = [ { description : "A collection of navigational components that compose declaratively with your app" , id : 1 , name : "React Router" , price : 8 }, { description : "A library that helps manage state across your app" , id : 2 , name : "React Redux" , price : 12 }, { description : "A library that helps you interact with a GraphQL backend" , id : 3 , name : "React Apollo" , price : 12 } ]
创建另外一个ProductsPage.tsx
文件导入这些数据,
1 2 import * as React from "react" ;import { IProduct , products } from "./ProductsData" ;
因为需要在组件引用数据,创建一个接口,
1 2 3 interface IState { products : IProduct []; }
创建类组件,初始化状态,
1 2 3 4 5 6 7 8 9 10 class ProductsPage extends React.Component <{}, IState > { public constructor (props: {} ) { super (props); this .state = { products : [] }; } } export default ProductsPage ;
实现componentDidMount
生命周期方法,更新组件的State,
1 2 3 public componentDidMount ( ) { this .setState ({ products }); }
实现对应的render
方法进行渲染,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public render ( ) { return ( <div className ="page-container" > <p > Welcome to React Shop where you can get all your tools for ReactJS!</p > <ul className ="product-list" > {this.state.products.map(product => ( <li key ={product.id} className ="product-list-item" > {product.name} </li > ))} </ul > </div > ); }
对应CSS样式为,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .page-container { text-align : center; padding : 20px ; font-size : large; } .product-list { list-style : none; margin : 0 ; padding : 0 ; } .product-list-item { padding : 5px ; }
实现第二个页面,文件名为AdminPage.tsx
,它是个无状态组件,
1 2 3 4 5 6 7 8 9 10 11 12 import * as React from "react" ;const AdminPage : React .SFC = () => { return ( <div className ="page-container" > <h1 > Admin Panel</h1 > <p > You should only be here if you have logged in</p > </div > ); } export default AdminPage ;
现在我们有两个页面了,需要为其定义两个路由。首先创建一个Routes.tsx
的文件,包含下面的内容,
1 2 3 4 import * as React from "react" ;import { BrowserRouter as Router , Route } from "react-router-dom" ;import AdminPage from "./AdminPage" ;import ProductsPage from "./ProductsPage" ;
渲染路由页面,
1 2 3 4 5 6 7 8 9 10 11 12 const Routes : React .SFC = () => { return ( <Router > <div > <Route path ="/products" component ={ProductsPage} /> <Route path ="/admin" component ={AdminPage} /> </div > </Router > ); }; export default Routes ;
最后一步,把Routes
添加到根组件index.tsx
中,
1 2 3 4 5 6 7 import React from 'react' ;import ReactDOM from 'react-dom' ;import './index.css' ;import Routes from "./Routes" ;ReactDOM .render (<Routes /> , document .getElementById ('root' ) as HTMLElement );
目前页面上是什么也没有看到的,需要我们在浏览器直接输入地址,“/products
”,或者访问另一个页面"/admin
",
创建路由向导
非常幸运的是,React Router有一些组件提供了向导的功能。
Using the Link component
使用Link
组件实现向导功能,
创建一个Header.tsx
文件,包含以下内容,
1 2 3 4 import * as React from "react" ;import { Link } from "react-router-dom" ;import logo from "./logo.svg" ;
接着创建两个Link
链接,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const Header : React .SFC = () => { return ( <header className ="header" > <img src ={logo} className ="header-logo" alt ="logo" /> <h1 className ="header-title" > React Shop</h1 > <nav > <Link to ="/products" className ="header-link" > Products</Link > <Link to ="/admin" className ="header-link" > Admin</Link > </nav > </header > ); } export default Header ;
添加对应的CSS样式,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 .header { text-align : center; background-color : #222 ; height : 160px ; padding : 20px ; color : white; } .header-logo { animation : header-logo-spin infinite 20s linear; height : 80px ; } @keyframes header-logo-spin { from { transform : rotate (0deg ); } to { transform : rotate (360deg ); } } .header-title { font-size : 1.5em ; } .header-link { color : #fff ; text-decoration : none; padding : 5px ; }
把Header
添加到我们的Routes.tsx
,
1 import Header from "./Header" ;
加入导航,
1 2 3 4 5 6 7 <Router > <div > <Header /> <Route path ="/products" component ={ProductsPage} /> <Route path ="/admin" component ={AdminPage} /> </div > </Router >
使用NavLink组件
React Router还提供了另外一个组件用于页面链接,称为NavLink
。下面我们用NavLink
重构一下原来的Header
组件,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import * as React from "react" ;import { NavLink } from "react-router-dom" ;import logo from "./logo.svg" ;const Header : React .SFC = () => { return ( <header className ="header" > <img src ={logo} className ="header-logo" alt ="logo" /> <h1 className ="header-title" > React Shop</h1 > <nav > <NavLink to ="/products" className ="header-link" > Products</NavLink > <NavLink to ="/admin" className ="header-link" > Admin</NavLink > </nav > </header > ); } export default Header ;
NavLink
暴露了一个activeClassName
属性
1 2 <NavLink to="/products" className="header-link" activeClassName="header-link-active" >Products </NavLink > <NavLink to ="/admin" className ="header-link" activeClassName ="header-link-active" > Admin</NavLink >
添加对应CSS样式,
1 2 3 4 .header-link-active { border-bottom : #ebebeb solid 2px ; }
因此,NavLink
主要让我们导航带上样式功能。
路由参数
路由参数是路径变量的部分,用于决定目标组件的渲染逻辑。
我们需要添加另外一个页面,来展示商品的描述内容和价格,我们想要让页面导向使用"/products/{id}
"路径,其中id
对应商品ID。
首先在原来的Routes.tsx
添加路由路径和参数,
1 2 3 <Route path="/products" component={ProductsPage } /> <Route path ="/products/:id" component ={ProductPage} /> <Route path ="/admin" component ={AdminPage} />
创建对应的ProductPage
组件,
1 2 3 import * as React from "react" ;import { RouteComponentProps } from "react-router-dom" ;import { IProduct , products } from "./ProductsData" ;
这里的关键点是,我们需要用到RouteComponentProps
来访问路径上的参数id
。另外需要定义类型别名
1 type Props = RouteComponentProps <{id : string }>;
另外还需要有个状态记录商品被添加到购物篮中,
1 2 3 4 interface IState { product?: IProduct ; added : boolean ; }
初始化该状态,
1 2 3 4 5 6 7 8 class ProductPage extends React.Component <Props , IState > { public constructor (props: Props ) { super (props); this .setState ({ added : false }); } } export default ProductPage ;
当组件被加载进DOM,会通过路径参数上的id查找商品。RouteComponentProps
给我们提供了一个match
对象,以访问路径上的参数,
1 2 3 4 5 6 7 8 public componentDidMount ( ) { if (this .props .match .params .id ) { const id : number = parseInt (this .props .match .params .id , 10 ); const product = products.filter (p => p.id === id)[0 ]; this .setState ({ product }); } } }
记住,路径参数id是个字符串,需要使用parseInt
进行转换,
初始化好商品和组件状态后,我们进入到render
函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public render ( ) {const product = this .state .product ;return ( <div className ="page-container" > {product ? ( <React.Fragment > <h1 > {product.name}</h1 > <p > {product.description}</p > <p className ="product-price" > {new Intl.NumberFormat("en-US", { currency: "USD", style: "currency" }).format(product.price)} </p > {!this.state.added && ( <button onClick ={this.handleAddClick} > Add to basket</button > )} </React.Fragment > ) : ( <p > Product not found!</p > )} </div > ); }
这里有一些新东西,
第一行创建了常量product
;
三元运算符
使用了React.Fragment
,类似实现单独一个parent的功能
使用了Intl.NumberFormat
装换货币符号
另外还需要添加handleAddClick
方法处理按钮事件,
1 2 3 private handleAddClick = () => { this .setState ({ added : true }); };
现在,将我们实现好的ProductPage
组件,导入到Routes.tsx
中,
1 import ProductPage from "./ProductPage" ;
直接浏览器键入"/products/2
"查看新路由页面,但有个问题是"/products
"和"/products/:id
"都被渲染了
为了解决这个问题,修改为,
1 <Route exact={true } path="/products" component={ProductsPage } />
进一步,我们需要为每个条目添加链接,回到原来的ProductsPage.tsx
组件,修改相应部分内容,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { Link } from "react-router-dom" ;... public render ( ) { return ( <div className ="page-container" > <p > Welcome to React Shop where you can get all your tools for ReactJS! </p > <ul className ="product-list" > {this.state.products.map(product => ( <li key ={product.id} className ="product-list-item" > <Link to ={ `/products /${product.id }`}> {product.name}</Link > {product.name} </li > ))} </ul > </div > ); } ...
补充CSS样式,
1 2 3 .product-list-item a { text-decoration : none; }
Handling not found routes
如果用户输入的路径不存在怎么处理?例如,我们尝试键入"/tools",但路由中没有找到任何匹配的路由,我们希望提示该路径不存在。
首先创建一个新的文件NotFoundPage.tsx
,
1 2 3 4 5 6 7 8 9 10 11 import * as React from "react" ;const NotFoundPage : React .SFC = () => { return ( <div className ="page-container" > <h1 > Sorry, this page cannot be found</h1 > </div > ); }; export default NotFoundPage ;
在路由组件中,导入,
1 import NotFoundPage from "./NotFoundPage" ;
添加到路由,
1 2 3 4 5 6 7 8 9 <Router > <div > <Header /> <Route exact ={true} path ="/products" component ={ProductsPage} /> <Route path ="/products/:id" component ={ProductPage} /> <Route path ="/admin" component ={AdminPage} /> <Route component ={NotFoundPage} /> </div > </Router >
但其他页面也一同渲染了,我们希望如果没有找到对应路由页面,仅渲染NotFoundPage
,这是需要用到Switch
组件。
首先导入Swith
组件到Routes.tsx
中,
1 import { BrowserRouter as Router , Route , Switch } from "react-router-dom" ;
在Route
包一层Switch
组件即可,
1 2 3 4 5 6 <Switch > <Route exact ={true} path ="/products" component ={ProductsPage} /> <Route path ="/products/:id" component ={ProductPage} /> <Route path ="/admin" component ={AdminPage} /> <Route component ={NotFoundPage} /> </Switch >
Switch
组件仅渲染第一个匹配到的Route
组件。在我们案例中,当找不到页面是,第一个匹配到的就是NotFoundPage
,这样就解决了我们的问题。
实现页面重定向
页面重定向使用了Redirect
组件实现
Simple redirect
如果我们访问/
路径,我们注意到它是个not found页面。我们希望当路径是/
时,重定向到/products
。
首先,需要在Routes.tsx
导入Redirect
组件,
1 import { BrowserRouter as Router , Redirect , Route , Switch } from "react-router-dom" ;
然后使用Redirect
组件进行重定向,
1 2 3 4 5 6 7 <Switch > <Redirect exact ={true} from ="/" to ="/products" /> <Route exact ={true} path ="/products" component ={ProductsPage} /> <Route path ="/products/:id" component ={ProductPage} /> <Route path ="/admin" component ={AdminPage} /> <Route component ={NotFoundPage} /> </Switch >
Conditional redirect
另外我们可以用Redirect
组件对为授权用户进行保护访问。例如,我们的shop例子中,我们仅确保登录用户可以访问我们的Admin
页面。
创建一个LoginPage
路由,
1 <Route path="/login" component={LoginPage } />
添加一个登录页LoginPage.tsx
组件,
1 2 3 4 5 6 7 8 9 10 11 12 import * as React from "react" ;const LoginPage : React .SFC = () => { return ( <div className ="page-container" > <h1 > Login</h1 > <p > You need to login... </p > </div > ); }; export default LoginPage ;
回到原来的Routes.tsx
导入LoginPage
:
1 import LoginPage from "./LoginPage" ;
在重定向到"admin
"之前,我们需要在Routes.tsx
中添加一些状态值,
1 2 3 4 5 6 7 8 const Routes : React .SFC = () => { const [loggedIn, setLoggedIn] = React .useState (false ); return ( <Router > ... </Router > ); };
最后一步是添加条件判断,
1 2 3 <Route path="/admin" > {loggedIn ? <AdminPage /> : <Redirect to ="/login" /> } </Route >
如果我们将loggedIn
状态修改为true,我们就可以再次访问我们的Admin页面了,
1 const [loggedIn, setLoggedIn] = React .useState (true );
Query parameters
查询参数是URL地址的一部分,例如"/products?search=redux
"。让我们实现一个商品查询功能。
在原来的ProductsPage.tsx
中,添加一个状态变量search
,
1 2 3 4 interface IState { products : IProduct []; search : string ; }
我们需要用到RouteComponentProps
作为属性,
1 import { RouteComponentProps } from "react-router-dom" ;
初始化
1 2 3 4 5 6 7 8 9 class ProductsPage extends React.Component <RouteComponentProps , IState > { public constructor (props: RouteComponentProps ) { super (props); this .state = { products : [], search : "" }; } ...
我们需要在componentDidMount
确定好search
的值,因此,实现getDerivedStateFromProps
方法获取URL参数,并更新state,
1 2 3 4 5 6 7 8 9 10 11 public static getDerivedStateFromProps ( props: RouteComponentProps, state: IState ) { const searchParams = new URLSearchParams (props.location .search ); const search = searchParams.get ("search" ) || "" ; return { products : state.products , search }; }
不幸的是,URLsearchParams
没有在所有浏览器中实现,所以我们需要用到url-search-params-polyfill
,
1 yarn add -D url-search-params-polyfill
导入到ProductPages.tsx
中,
1 import "url-search-params-polyfill" ;
在渲染部分使用search
状态,并包装一个if
语句,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <ul className="product-list" > {this .state .products .map (product => { if ( !this .state .search || (this .state .search && product.name .toLowerCase ().indexOf (this .state .search .toLowerCase ()) > -1 ) ) { return ( <li key ={product.id} className ="product-list-item" > <Link to ={ `/products /${product.id }`}> {product.name}</Link > </li > ); } else { return null ; } })} </ul>
替换为搜索框搜索的方式… 我们需要,
在Header.tsx
文件中创建state,
1 const [search, setSearch] = React .useState ("" );
另外需要获取搜索参数,需要导入,
1 2 3 import { NavLink , RouteComponentProps , withRouter} from "reactrouter- dom" ;import "url-search-params-polyfill" ;
添加props
,
1 const Header : React .SFC <RouteComponentProps > = props => { ... }
组件首次渲染时,从路径参数获取值并设置search
的状态,
1 2 3 4 5 6 const [search, setSearch] = React .useState ("" );React .useEffect (() => {const searchParams = new URLSearchParams (props.location .search );setSearch (searchParams.get ("search" ) || "" );}, []);
将search
添加到redner
方法中,让用户进行输入,
1 2 3 4 5 6 7 8 9 10 11 12 ... <header className="header" > <div className ="search-container" > <input type ="search" placeholder ="search" value ={search} onChange ={handleSearchChange} onKeyDown ={handleSearchKeydown} /> </div > ...
添加search-container
CSS样式,
1 2 3 4 .search-container {text-align : right;margin-bottom : -25px ;}
回到原来的Header.tsx
,添加对应的事件处理方法,
1 2 3 4 5 6 7 8 9 10 11 const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement> ) => {setSearch (e.currentTarget .value );}; const handleSearchKeydown = (e: React.KeyboardEvent<HTMLInputElement> ) => {if (e.key === "Enter" ) {props.history .push (`/products?search=${search} ` ); } };
另外我们需要用withRouter
包装Header
暴露接口,以使this.props.history
可以生效,
1 export default withRouter (Hader );
Route prompts
react-router-dom
组件中还有一个Prompt
组件,顾名思义就是弹出框…
我们希望用户离开商品页面时,如果没有购物,则弹出提示,首先,在ProductPage.tsx
中导入Prompt
组件,
1 import { Prompt , RouteComponentProps } from "react-router-dom" ;
添加对应触发条件即可,
1 2 3 4 5 <div className="page-container" > <Prompt when ={!this.state.added} message ={this.navAwayMessage} /> ... private navAwayMessage = () => "Are you sure you leave without buying this product?" ;
Nested routes
内嵌路由就是在某一个一级URL下渲染多个组件。
譬如我们想要创建一个3层渲染,
第一层包含Users
和Products
的链接
Users
层又包含所有用户
点击每个用户可以看到具体信息
首先修改AdminPage.tsx
,导入向导组件,
1 import { NavLink , Route , RouteComponentProps } from "react-router-dom" ;
我们将会使用NavLink
组件渲染菜单
Route
用于内嵌路由
RouteComponentProps
类型将会获取URL的参数id
将p
标签内容替换为下面…
1 2 3 4 5 6 7 8 9 10 11 <div className="page-container" > <h1 > Admin Panel</h1 > <ul className ="admin-sections" > <li key ="users" > <NavLink to ={ `/admin /user `} activeClassName ="admin-link-active" > Users</NavLink > </li > <li key ="products" > <NavLink to ={ `/admin /products `} activeClassName ="admin-link-active" > Products</NavLink > </li > </ul > </div>
添加对应样式…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .admin -sections { list-style : none; margin : 0px 0px 20px 0px; padding : 0 ; } .admin -sections li { display : inline-block; margin-right : 10px; } .admin -sections li a { color : #222 ; text-decoration : none; } .admin -link-active { border-bottom : #6f6e6e solid 2px; }
回到原来的AdminPage.tsx
,添加两个路由组件,
1 2 3 4 5 6 7 8 <div className="page-container" > <h1 > Admin Panel</h1 > <ul className ="admin-sections" > ... </ul > <Route path ="/admin/users/:id" component ={AdminUsers} /> <Route path ="/admin/products" component ={AdminProducts} /> </div>
创建这两个路由组件,在AdminPage.tsx
内添加,
1 2 3 const AdminProducts : React .SFC = () => { return <div > Some options to administer products</div > ; };
接下来的AdminUsers
组件可能会复杂一些。首先定义一种数据结构,在AdminPage.tsx
的组件AdminProducts
下,
1 2 3 4 5 6 7 8 9 10 11 interface IUser { id : number ; name : string ; isAdmin : boolean ; } const adminUsersData : IUser [] = [ { id : 1 , name : "Fred" , isAdmin : true }, { id : 2 , name : "Bob" , isAdmin : false }, { id : 3 , name : "Jane" , isAdmin : true } ];
这样一来就有3个用户了。
接下来实现AdminUsers
组件的内容,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const AdminUsers : React .SFC = () => { return ( <div > <ul className ="admin-sections" > {adminUsersData.map(user => ( <li > <NavLink to ={ `/admin /users /${user.id }`} activeClassName ="admin-link-active" > {user.name} </NavLink > </li > ))} </ul > </div > ); };
该组件会渲染每个用户名的链接。它是一个内嵌路径,
另外,还需要定义另外一个路由展示用户详细信息。添加一个路由,
1 2 3 4 5 6 <div> <ul className ="admin-sections" > ... </ul > <Route path ="/admin/users/:id" component ={AdminUser} /> </div>
这个AdminUser
也还没有实现,因此,在AdminUsers
组件下面添加上,
1 2 3 const AdminUser : React .SFC <RouteComponentProps <{ id : string }>> = props => { return null ; };
我们用到了RouteComponentProps
通过id
来获取可用的属性。
然后通过id
获取定义的adminUsersData
中的记录,
1 2 3 4 5 6 7 8 9 10 const AdminUser : React .SFC <RouteComponentProps <{ id : string }>> = props => { let user : IUser ; if (props.match .params .id ) { const id : number = parseInt (props.match .params .id , 10 ); user = adminUsersData.filter (u => u.id === id)[0 ]; } else { return null ; } return null ; };
有了user
对象后,渲染其内容,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const AdminUser : React .SFC <RouteComponentProps <{ id : string }>> = props => { let user : IUser ; if (props.match .params .id ) { const id : number = parseInt (props.match .params .id , 10 ); user = adminUsersData.filter (u => u.id === id)[0 ]; } else { return null ; } return ( <div > <div > <b > Id: </b > <span > {user.id.toString()}</span > </div > <div > <b > Is Admin: </b > <span > {user.isAdmin.toString()}</span > </div > </div > ); };
再次运行,进入到Admin
页面,点击Products
… 点击Users
…
因此,如果需要实现内嵌路由,需要用到NavLink
或Link
组件,以及使用Route
组件渲染这些内容。
Animated transitions
本小节将给不同的导航添加动画。我们会使用react-transition-group
中的TransitionGroup
和CSSTransition
组件,步骤如下,
首先安装对应这些包,
1 2 npm install react-transition-group npm install @types /react-transition-group --save-dev
TransitionGroup
会持续跟踪它内部本地state的子组,并计算子组进入和退出。CSSTransition
则在TransitionGroup
子组离开或退出时,提供CSS类来表示一种状态。
因此,TransitionGroup
和CSSTransition
可以包装我们的路由,并调用CSS类,
首先我们需要在我们的Routes.tsx
中导入这些组件,
1 import { CSSTransition , TransitionGroup } from "react-transition-group" ;
我们还需要路由属性,
1 import { Redirect , Route , RouteComponentProps , Switch } from "react-router-dom" ;
作用我们的Route
组件,
1 2 const Routes : React .SFC <RouteComponentProps > = props => {...
用CSSTransition
和TransitionGroup
来包装我们的路由,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <TransitionGroup > <CSSTransition key ={props.location.key} timeout ={500} classNames ="animate" > <Switch > <Redirect exact ={true} from ="/" to ="/products" /> <Route exact ={true} path ="/products" component ={ProductsPage} /> <Route path ="/products/:id" component ={ProductPage} /> <Route path ="/admin" > {loggedIn ? <AdminPage /> : <Redirect to ="/login" /> } </Route > <Route path ="/login" component ={LoginPage} /> <Route component ={NotFoundPage} /> </Switch > </CSSTransition > </TransitionGroup >
TransitionGroup
要求子组要有一个唯一的key
决定退出和进入的动作。因此我们在CSSTransition
上指定key
属性为RouteComponentProps
上的location.key
。我们设置了transtion的timeout属性为半秒,以及它的样式类。
接下来,我们需要添加这些CSS类到index.css
中,
1 2 3 4 5 6 7 8 9 10 11 12 13 .animate-enter { opacity : 0 ; z-index : 1 ; } .animate-enter-active { opacity : 1 ; transition : opacity 450ms ease-in; } .animate-exit { display : none; }
当它的key
改变时,CSSTransition
会调用这些CSS样式。
因为我们不能在Router
组件外部使用高阶组件withRouter
。要解决这个问题,可以添加一个新的组件RoutesWrap
进行包装,在原来的Routes.tsx
中添加,
1 2 3 4 5 6 7 8 9 10 11 12 13 const RoutesWrap : React .SFC = () => { return ( <Router > <Route component ={Routes} /> </Router > ); }; class Routes extends React.Component <RouteComponentProps ,IState > {... } export default RoutesWrap ;
在Routes
组件中移除Router
,让div
标签作为它的根。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public render ( ) {return (<div > <Header /> <TransitionGroup > ... </TransitionGroup > </div > ); }`` ` 再次执行程序,可以看到进入不同页面是有一种渐退效果。 ## Lazy loading routes 截至目前为止,所有JavaScript会在app第一次加载时全部加载。包括` Admin `这些我们不需要的页面。这一点会造成一些资源浪费。所以需要用到一种称为“lazy loading”的组件。 1. 首先我们需要导入` Suspense `这个组件, ` `` tsximport { Suspense } from "react" ;
接着以另一种方式导入这个AdminPage
组件,
1 const ADminPage = React .lazy (() => import ("./AdminPage" ));
我们使用了React的一个函数lazy
,它接收一个函数并返回一个动态导入,换句话说就是指派了一个AdminPage
组件变量。
上面步骤会获得一个lint错误,A dynamic import call in ES5/ES3 requires the ‘Promise’ constructor. Make sure you have a declaration for the ‘Promise’ constructor or include ‘ES2015’ in your --lib
option. 需要在tsconfig.json
中添加lib
选项。
1 2 3 4 "compilerOptions" : { "lib" : ["es6" , "dom" ], ... }
接下来的内容是,使用Suspense
组件包装这个AdminPage
组件,
1 2 3 4 5 6 7 8 9 10 <Route path="/admin" > {loggedIn ? ( <Suspense fallback ={ <div className ="pagecontainer" > Loading...</div > }> <AdminPage /> </Suspense > ) : ( <Redirect to ="/login" /> )} </Route >
Suspense
组件会在AdminPage
被加载时,展示这个div
部分内容。有点说明的是,组件加载非常快,可能实际浏览器看了没效果,需要进入Network 选择Slow 3G 网络环境模式。刷新页面便可看到出现Loading…
总结
React Router给我们一套全面的组件管理导航页。最先学习的是底层组件Router
,它会查找下游的Route
组件并根据路径进行渲染。
Link
组件让我们在应用中链接到不同的页面。我们学习了NavLinke
和Link
相似,但提供了对不同激活路径的样式功能。因此NavLink
最大好处用在侧边栏或标头横幅,Link
用在内嵌页面跳转。
RouteComponentProps
是一种泛型类型,它提供了访问路径参数的能力。我们还发现了React Router并不能解析路径参数,但可以使用本地的JavaScript的URLSearchParams
实现。
Redirect
组件提供了路径重定向的功能。这非常适用于对权限页面的控制。
Prompt
组件提供了弹窗的能力。
我们还学习了通过使用TransitionGroup
和CSSTransition
组件,通过包装我们的Route
组件实现一种渐退的效果。
另外我们还学习了使用Suspense
组件配合React的lazy
函数,对不同的页面实现延迟加载,帮助我们提升应用的性能效果。