React Router 4是一组导航组件,在你的React应用程序中提供声明性路由。在这个教程中,将经过一个实际的例子来介绍React Router 4是怎样使用的。
在几乎每一个应用程序的体系结构中,路由都是极其重要的。应用程序越大,路由功能就越复杂,从简单到深度嵌套的路由场景。css
在React构建的应用程序中,React Router 是最受欢迎使用最广泛的路由库,随着你的应用程序不断变大变复杂,那就须要多个视图和路由,选择一个好的路由库来帮助管理视图之间的转换、重定向、获取URL参数等,让这些操做变得更加的简单,方便。react
在此以前,以前版本的React Router涉及预先声明应用程序的路由,在呈现以前声明文件中的全部路径做为应用程序初始化的一部分。使用React Router 4,你能够以声明方式进行路由。 React Router 4的API基本上都是组件,所以若是你已经在React中组合了组件,它就很容易使用。让咱们开始吧!web
你须要:npm
React Router有这几个包组成:react-router
、react-router-dom
和react-router-native
。数组
使用create_react_app建立一个新项目,而后导航到以下所示建立的目录。浏览器
create-react-app bose cd bose
安装 react-router-dom
。react-router
npm install --save react-router-dom
咱们将覆盖哪些内容?app
咱们将专一于在浏览器端使用React Router 4。咱们将介绍下面列出的很是重要的概念:dom
在React应用中有两种路由组件可用,BrowserRouter
和 HashRouter
,前者组成的url不带#,然后者则带有。ide
注意:若是要构建支持旧版浏览器的Web应用程序,建议使用HashRouter。
打开src/index.js文件,并添加如下代码:
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Route, Link } from "react-router-dom"; import './index.css'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; ReactDOM.render( <Router> <App /> </Router>, document.getElementById('root')); registerServiceWorker();
在上面代码中,咱们从react-router-dom
导入了BrowserRouter
, Route
, 和 Link
组件,并经过Router组件(是BrowserRouter的别名)包裹App组件。路由器组件是成功路由的第一步,它充当其余每一个路由组件的容器。另外,Router
组件只能有惟一一个子元素或子组件。如今,咱们该怎样定义路由呢?
打开src/App.js
,咱们将路由定义在这里。
import React, { Component } from 'react'; import { Route, Link } from 'react-router-dom'; import './App.css'; const Home = () => ( <div> <h2> Home </h2> </div> ); const Airport = () => ( <div> <ul> <li>Jomo Kenyatta</li> <li>Tambo</li> <li>Murtala Mohammed</li> </ul> </div> ); const City = () => ( <div> <ul> <li>San Francisco</li> <li>Istanbul</li> <li>Tokyo</li> </ul> </div> ); class App extends Component { render() { return ( <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/airports">Airports</Link></li> <li><Link to="/cities">Cities</Link></li> </ul> <Route path="/" component={Home}/> <Route path="/airports" component={Airport}/> <Route path="/cities" component={City}/> </div> ); } } export default App;
在上面代码中,使用了Link
组件将用户定向到/
, /airports
, 和/cities
。这些连接中的每个都有一个组件,一旦当前位置与路由的路径匹配,就应该渲染该组件。然而,事实并非这样的。让咱们接着往下看。
Airports route
做为Home
组件的视图Home
应仅匹配/
时,才在根组件上显示。而后它在全部的路由上都渲染了。路径/
匹配/airports
和/cities
路由。所以会在其余两个路由中渲染Home
组件,那么比较简单的解决方法是在/
的路由组件上添加exact
。
src/App.js
<Route path="/" exact component={Home}/> <Route path="/airports" component={Airport}/> <Route path="/cities" component={City}/>
Airports
路由如今就没有渲染Home
UI组件了*。在上面的例子中,全部的<Route />
组件有一个component
属性,做用是当访问的URL匹配所配置的路由的路径时,渲染一个组件。若是你只想渲染一个小函数而不是整个组件,该怎么办?正以下面的代码同样,你可使用render
属性来展现。
<Route path="/airports" render={() => (<div> This is the airport route </div>)}/>
若是你但愿URL像/courses/business
,/courses/technology/
,你会怎么去实现它?
src/App.js
import React, { Component } from 'react'; import { Route, Link } from 'react-router-dom'; import './App.css'; const Courses = ({ match }) => ( <div> <ul> <li><Link to="/courses/technology">Technology</Link></li> <li><Link to="/courses/business">Business</Link></li> <li><Link to="/courses/economics">Economics</Link></li> </ul> <Route exact path="/courses/technology" render={() => (<div> This is technology </div>)}/> <Route path="/courses/business" component={() => (<div> This is business </div>)}/> <Route path="/courses/economics" component={() => (<div> This is economics </div>)}/> </div> ); /* Home Component */ // code hidden /* City Component */ //code hidden class App extends Component { render() { return ( <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/courses">Courses</Link></li> <li><Link to="/cities">Cities</Link></li> </ul> <Route path="/" exact component={Home}/> <Route path="/courses" component={Courses}/> <Route path="/cities" component={City}/> </div> ); } } export default App;
若是当前的URL匹配路径/courses
,而后经过Courses
组件渲染了technology、business、economics连接,更进一步来讲,若是当前的URL匹配/courses/technology
、/courses/business
和 /courses/economics
等的路径,This is technology
, This is business
与This is economics
就可以渲染呈如今页面上。
做为一名开发人员,我相信你已经用一双重构眼睛看着这种方法了。在上面比较简单的代码中,有不少重复和硬编码。代码行越多,改变理由就越难。让咱们来重构一下。
React Router 4附带了一个匹配API,当一个路由的路径成功与当前的URL匹配时建立这个匹配对象。这个匹配对象有一些属性,可是我将列出你应该立刻知道的一些属性:
让咱们一步一步的重构,就像这样使用匹配对象来重构Courses
组件:
const Courses = ({ match }) => ( <div> <ul> <li><Link to={`${match.url}/technology`}>Technology</Link></li> <li><Link to={`${match.url}/business`}>Business</Link></li> <li><Link to={`${match.url}/economics`}>Economics</Link></li> </ul> <Route exact path="/courses/technology" render={() => (<div> This is technology </div>)}/> <Route path="/courses/business" component={() => (<div> This is business </div>)}/> <Route path="/courses/economics" component={() => (<div> This is economics </div>)}/> </div> );
测试一下若是你的URL是正常运行的,使用match.path
来对Route
组件作一样的事。
const Courses = ({ match }) => ( <div> <ul> <li><Link to={`${match.url}/technology`}>Technology</Link></li> <li><Link to={`${match.url}/business`}>Business</Link></li> <li><Link to={`${match.url}/economics`}>Economics</Link></li> </ul> <Route exact path={`${match.path}/technology`} render={() => (<div> This is technology </div>)}/> <Route path={`${match.path}/business`} component={() => (<div> This is business </div>)}/> <Route path={`${match.path}/economics`} component={() => (<div> This is economics </div>)}/> </div> );
检查你的应用,应该是处于较好的运行状态的。如今最后一步,咱们可使用一行代码来替换上面的三行代码。
const Courses = ({ match }) => ( <div> <ul> <li><Link to={`${match.url}/technology`}>Technology</Link></li> <li><Link to={`${match.url}/business`}>Business</Link></li> <li><Link to={`${match.url}/economics`}>Economics</Link></li> </ul> <Route exact path={`${match.path}/:course`} render={({match}) => (<div> This is {match.params.course} </div>)}/> </div> );
咱们使用了match.params
,它针对URL的位置提供了一个键值对的对象。:course
是URL的参数。所以match.params.course
针对正确的URL提供了一个值。
当开发一个web应用时,在某些状况下,必须保护某些路由不被访问。在大多数状况下,这些路由只能被受权用户所访问。
在以前的React Router版本,如v3。保护路由的代码像下面这样:
index.js
const Root = () => { return ( <div className="container"> <Router history={browserHistory}> <Route path="/" component={Display}/> <Route path="/upload" component={Upload} onEnter={requireAuth} /> <Route path="/callback" component={Callback} /> </Router> </div> ) }
<Route />
组件有一个onEnter
属性,它接受一种容许根据用户的身份验证状态输入或拒绝URL位置的方法。如今,它与React Router 4不一样。
让咱们来建立三个组件,分别是Public,
Private
, Login
。
App.js
import React, { Component } from 'react'; import { Route, Link, BrowserRouter as Router, } from 'react-router-dom'; const Public = () => ( <div> This is a public page </div> ); const Private = () => ( <div> This is a private page </div> ); const Login = () => ( <div> Login Page <button>login</button> </div> ); class App extends Component { render() { return ( <Router> <div style={{width: 1000, margin: '0 auto'}}> <ul> <li><Link to='/public'> Public </Link></li> <li><Link to='/private'> Private </Link></li> </ul> <hr/> <Route path='/public' component={Public} /> <Route path='/private' component={Private} /> </div> </Router> ); } } export default App;
如今咱们可以访问/public
, /private
这两个路由。如今,让咱们来确保/private
路由不能被访问,知道用户已经登陆了。React Router 4使用了一个声明式的方法,因此咱们可以方便地使用一个如<SecretRoute />
的组件,然而react router 4并无提供它,咱们来构建它。咱们想到了一个受权服务。
在这个例子中,受权服务(Auth Service)是一个以下简单的对象:
const AuthService = { isAuthenticated: false, authenticate(cb) { this.isAuthenticated = true setTimeout(cb, 100) }, logout(cb) { this.isAuthenticated = false setTimeout(cb, 100) } }
如今,咱们来构建<SecretRoute />
:
const SecretRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={(props) => ( AuthService.isAuthenticated === true ? <Component {...props} /> : <Redirect to='/login' /> )} /> );
上面的代码简单地说明了,当受权状态对于用户是true,组件将渲染,不然用户可能被重定向到/login
路由,让咱们来试一下。
App.js
import React, { Component } from 'react'; import { Route, Link, Redirect, BrowserRouter as Router, } from 'react-router-dom'; const Login = () => ( <div> Login Page <button>login</button> </div> ); const AuthService = { isAuthenticated: false, authenticate(cb) { this.isAuthenticated = true setTimeout(cb, 100) }, logout(cb) { this.isAuthenticated = false setTimeout(cb, 100) } }; const SecretRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={(props) => ( AuthService.isAuthenticated === true ? <Component {...props} /> : <Redirect to='/login' /> )} /> ); class App extends Component { render() { return ( <Router> <div style={{width: 1000, margin: '0 auto'}}> <ul> <li><Link to='/public'> Public </Link></li> <li><Link to='/private'> Private </Link></li> </ul> <hr/> <Route path='/public' component={Public} /> <SecretRoute path='/private' component={Private} /> </div> </Router> ); } } export default App;
当连接Private
连接时,被迫重定向到了/login
路由。很好,让咱们更进一步经过尝试实际的登陆和注销流程。像下面这样修改login组件:
App.js
... class Login extends React.Component { state = { redirectToPreviousRoute: false }; login = () => { AuthService.authenticate(() => { this.setState({ redirectToPreviousRoute: true }); }); }; render() { const { from } = this.props.location.state || { from: { pathname: "/" } }; const { redirectToPreviousRoute } = this.state; if (redirectToPreviousRoute) { return <Redirect to={from} />; } return ( <div> <p>You must log in to view the page at {from.pathname}</p> <button onClick={this.login}>Log in</button> </div> ); } }
咱们修改了登陆组件,增长了一个login方法而且当用户拒绝访问的时候重定向到用户想要登陆进入的组件,这对于你的路由系统来讲是一种典型的行为,而且当用户访问的时候,会重定向到另外一个页面。
如今咱们在<SecretRoute />
中咱们不得不修改<Redirect />
组件中的属性:
const SecretRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={(props) => ( AuthService.isAuthenticated === true ? <Component {...props} /> : <Redirect to={{ pathname: '/login', state: { from: props.location } }} /> )} /> );
到这里差很少完成了。然而,当用户成功登录获取受权后提供一个退出登陆按钮是否是更好呢?让咱们建立一个<AuthStatus />
组件。
App.js
... const AuthStatus = withRouter(({ history }) => ( AuthService.isAuthenticated ? ( <p> Welcome! <button onClick={() => { AuthService.logout(() => history.push('/')) }}>Sign out</button> </p> ) : ( <p>You are not logged in.</p> ) ));
在上面简单的示例代码中,咱们使用了withRouter
和history.push
。其中withRouter
是一个来自React Router 的高阶组件,当拥有相同属性的路由改变的时候它可以从新渲染组件。history.push
是使用React Router中的<Redirect />
组件重定的一种方法。
如今,继续渲染<AuthStatus />
组件
App.js
class App extends Component { render() { return ( <Router> <div style={{width: 1000, margin: '0 auto'}}> <AuthStatus /> <ul> <li><Link to='/public'> Public </Link></li> <li><Link to='/private'> Private </Link></li> </ul> <hr/> <Route path='/public' component={Public} /> <Route path="/login" component={Login}/> <SecretRoute path='/private' component={Private} /> </div> </Router> ); } }
如今,从新在浏览器中试一下,你应该可以成功地登陆和退出登陆。
怎样自定义Link组件?其实很简单。你将学习如何自定义连接,以便在特定连接处于激活状态时具备独特外观,React Router 4有一种很容易实现这个任务的方法。
你的App.js
代码以下:
import React from 'react' import { BrowserRouter as Router, Route, Link } from 'react-router-dom' const Home = () => ( <div> <h2>Home Page</h2> </div> ) const Contact = () => ( <div> <h2>Contact Page</h2> </div> ) class App extends React.Component { render() { return ( <Router> <div> <CustomLink exact={true} to="/"> Home </CustomLink> <CustomLink to="/contact"> Contact </CustomLink> <hr/> <Route exact path="/" component={Home}/> <Route path="/contact" component={Contact}/> </div> </Router> ) } } export default App;
<CustomLink />
负责使管理不一样的处于激活状态的连接。
const CustomLink = ({ children, to, exact }) => ( <Route path={to} exact={exact} children={({ match }) => ( <div className={match ? 'active' : ''}> {match ? '> ' : ''} <Link to={to}> {children} </Link> </div> )}/> );
这并不复杂,<CustomLink>
可以驱动<Route>
。在上面的代码中,当路由路径匹配URL位置时,使用了match
对象来决定是否添加>
标志。
这里有三种渲染<Route>
组件的方式:<Route component>
, <Route render>
与<Route children>
。上面的代码使用了children
属性,此渲染属性接受一个函数,该函数接收与组件和渲染函数相同的全部路径属性,除非路径与URL位置不匹配。此过程使你可以根据路由是否匹配来动态调整UI。而且也是咱们须要的来建立一个自定义Link组件的方式。
做为一个开发者,你须要处理某个路由不存在的场景。若是一个用户访问了你的网站,而且访问了一个不存在的路由,如/babalawo
,你会作什么?难道你就这样准许你的网站挂了?接下来一块儿来处理这样的场景。
App.js
import React, { Component } from 'react'; import { Route, Link, Redirect, Switch, BrowserRouter as Router, } from 'react-router-dom'; const Home = () => ( <div> <h2>Home Page</h2> </div> ) const Contact = () => ( <div> <h2>Contact Page</h2> </div> ) class App extends Component { render() { return ( <Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/contact">Contact</Link> </li> </ul> <Switch> <Route exact path="/" component={Home}/> <Route path="/contact" component={Contact}/> <Route render={() => (<div> Sorry, this page does not exist. </div>)} /> </Switch> </div> </Router> ); } } export default App;
在上面的代码中,咱们引入了一个来自React Router新的组件<Switch>
,并将咱们的组件包裹在<Switch />
组件中。如今,若是访问的URL连接已所定义的带有路径的路由不匹配的话, <Switch />
组件引用了一个没有配置路径,且只有一个render方法的<Route />
。
在你的浏览器中试着访问一个不存在的URL,网页上将显示一条Sorry, this page does not exist的信息。
长时间里,侧边栏一直存在于app中,让咱们学习使用React Router 4怎样建立一个侧边栏。第一步就是将咱们的路由放在一个数组中:
import React from 'react' import { BrowserRouter as Router, Route, Link, } from 'react-router-dom' const routes = [ { path: '/', exact: true, leftbar: () => <div>Home</div>, main: () => <h2>Home</h2> }, { path: '/about', leftbar: () => <div>About</div>, main: () => <h2>About</h2> }, { path: '/contact', leftbar: () => <div>Contact</div>, main: () => <h2>Contact</h2> } ] class App extends React.Component { render() { return ( <Router> <div style={{ display: 'flex' }}> <div style={{ padding: '10px', width: '40%', background: '#FF6347' }}> <ul style={{ listStyleType: 'none', padding: 0 }}> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> <li><Link to="/contact">Contact</Link></li> </ul> </div> </div> </Router> ) } } export default App
在上面的代码中,咱们定义了一个leftbar
和main
的键,他们很快就会派上用场,让咱们的工做变得很是轻松。
如今咱们要作的就是遍历这个数组:
App.js
render() { return ( <Router> <div style={{ display: 'flex' }}> <div style={{ padding: '10px', width: '40%', background: '#FF6347' }}> <ul style={{ listStyleType: 'none' }}> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> <li><Link to="/contact">Contact</Link></li> </ul> {routes.map((route) => ( <Route key={route.path} path={route.path} exact={route.exact} component={route.leftbar} /> ))} </div> <div style={{ flex: 1, padding: '20px' }}> {routes.map((route) => ( <Route key={route.path} path={route.path} exact={route.exact} component={route.main} /> ))} </div> </div> </Router> ) }
上面的代码中,不管路由的路径如何匹配URL的位置,左边栏的组件都会从新渲染。