安装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-containerCSS样式,
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函数,对不同的页面实现延迟加载,帮助我们提升应用的性能效果。