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的位置,左邊欄的組件都會從新渲染。