React学习之React-Router V4实战

React Router 4是一组导航组件,在你的React应用程序中提供声明性路由。在这个教程中,将通过一个实际的例子来介绍React Router 4是怎样使用的。

在几乎每个应用程序的体系结构中,路由都是极其重要的。应用程序越大,路由功能就越复杂,从简单到深度嵌套的路由场景。

在React构建的应用程序中,React Router 是最受欢迎使用最普遍的路由库,随着你的应用程序不断变大变复杂,那就需要多个视图和路由,选择一个好的路由库来帮助管理视图之间的转换、重定向、获取URL参数等,让这些操作变得更加的简单,方便。

在此之前,之前版本的React Router涉及预先声明应用程序的路由,在呈现之前声明文件中的所有路径作为应用程序初始化的一部分。使用React Router 4,你可以以声明方式进行路由。 React Router 4的API基本上都是组件,因此如果你已经在React中组合了组件,它就很容易使用。让我们开始吧!

设置和安装

你需要:

  • 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-dom

npm install --save react-router-dom

我们将覆盖哪些内容?

我们将专注于在浏览器端使用React Router 4。我们将介绍下面列出的非常重要的概念:

  • 基本路由
  • 嵌套路由和url参数
  • 路由保护和认证
  • 自定义Link组件
  • 处理不存在的路由
  • 渲染SideBar

基本路由(Basic Routing)

在React应用中有两种路由组件可用,BrowserRouterHashRouter,前者组成的url不带#,而后者则带有。

注意:如果要构建支持旧版浏览器的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。这些链接中的每一个都有一个组件,一旦当前位置与路由的路径匹配,就应该渲染该组件。然而,事实并不是这样的。让我们接着往下看。

《React学习之React-Router V4实战》

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

《React学习之React-Router V4实战》

  • 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;

《React学习之React-Router V4实战》
现在我们能够访问/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;

《React学习之React-Router V4实战》
当链接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的信息。

《React学习之React-Router V4实战》

渲染侧边栏

长时间里,侧边栏一直存在于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>
  )
}

《React学习之React-Router V4实战》
上面的代码中,无论路由的路径如何匹配URL的位置,左边栏的组件都会重新渲染。

    原文作者:darkCode
    原文地址: https://segmentfault.com/a/1190000019852300
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞