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的位置,左边栏的组件都会从新渲染。

相关文章
相关标签/搜索