React學習之React-Router V4實戰

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

  • Node.js (6.0版本或更高版本) 和 npm.
  • create-react-app用於引導新項目。

React Router有這幾個包組成:react-routerreact-router-domreact-router-native數組

  • react-router: 包括核心路由組件。
  • react-router-dom:包含瀏覽器所需的路由API。
  • react-router-native:包含移動端應用所需的路由API。

使用create_react_app建立一個新項目,而後導航到以下所示建立的目錄。瀏覽器

create-react-app bose
cd bose

安裝 react-router-domreact-router

npm install --save react-router-dom

咱們將覆蓋哪些內容?app

咱們將專一於在瀏覽器端使用React Router 4。咱們將介紹下面列出的很是重要的概念:dom

  • 基本路由
  • 嵌套路由和url參數
  • 路由保護和認證
  • 自定義Link組件
  • 處理不存在的路由
  • 渲染SideBar

基本路由(Basic Routing)

在React應用中有兩種路由組件可用,BrowserRouterHashRouter,前者組成的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}/>

exact

  • Airports路由如今就沒有渲染Home UI組件了*。

在上面的例子中,全部的<Route />組件有一個component屬性,做用是當訪問的URL匹配所配置的路由的路徑時,渲染一個組件。若是你只想渲染一個小函數而不是整個組件,該怎麼辦?正以下面的代碼同樣,你可使用render屬性來展現。

<Route path="/airports"
       render={() => (<div> This is the airport route </div>)}/>

嵌套路由和URL參數

若是你但願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 businessThis is economics就可以渲染呈如今頁面上。

做爲一名開發人員,我相信你已經用一雙重構眼睛看着這種方法了。在上面比較簡單的代碼中,有不少重複和硬編碼。代碼行越多,改變理由就越難。讓咱們來重構一下。

React Router 4附帶了一個匹配API,當一個路由的路徑成功與當前的URL匹配時建立這個匹配對象。這個匹配對象有一些屬性,可是我將列出你應該立刻知道的一些屬性:

  • match.url: 返回一個顯示URL的字符串
  • match.path:返回一個顯示路由的路徑字符串
  • match.params:返回一個具備從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;

auth
當連接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>
  )
));

在上面簡單的示例代碼中,咱們使用了withRouterhistory.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組件

怎樣自定義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的信息。

book

渲染側邊欄

長時間裏,側邊欄一直存在於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

在上面的代碼中,咱們定義了一個leftbarmain的鍵,他們很快就會派上用場,讓咱們的工做變得很是輕鬆。

如今咱們要作的就是遍歷這個數組:

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>
  )
}

sss上面的代碼中,不管路由的路徑如何匹配URL的位置,左邊欄的組件都會從新渲染。

相關文章
相關標籤/搜索