探索React----第一章:ReactRouterV2基础

廖令
2023-12-01

ReactRouter(V2)基础

1.路由

路由就是Router,这里的路由并不是我们在计算机网路中所学的路由的概念,而是前端的‘路由’,简单的来说就是通过URL的变化,使用路由来跳转到相对应的路径来显示不一样的页面,虽然只是这么一个小功能,但是它却是web应用中一个非常非常非常重要的一步。一个只会写页面的前端工程师不是工程师,顶多就是个工人,工程师的目标就是去架构,去优化,去理解,去运用。所以ReactRouter是必须要迈过的一个坎,为了方便写文章,ReactRouter的知识得再从头学一遍。

2.路由配置

不使用ReactRouter

import React from 'react'
import { render } from 'react-dom'

//这是创建React组件的ES5的写法,ES6的写法是export default class extends React.component{}
//可以看到我们一共创建了4个页面组件,

const About = React.createClass({/*...*/})
const Inbox = React.createClass({/*...*/})
const Home = React.createClass({/*...*/})
//当前渲染的是App页面
const App = React.createClass({
  //初始化路由的状态为'#'后面的字符串
  getInitialState() {
    return {
      route: window.location.hash.substr(1)
    }
  },
  //在页面第一次渲染后随时监听URL上'#'后面的字符串来控制跳转
  componentDidMount() {
    window.addEventListener('hashchange', () => {
      this.setState({
        route: window.location.hash.substr(1)
      })
    })
  },
  //一旦state重新赋值后会触发当前页面重新渲染,这个时候APP页面的子组件会发生改变,改变的页面自然就是路由的值决定的
  render() {
    let Child
    switch (this.state.route) {
      case '/about': Child = About; break;
      case '/inbox': Child = Inbox; break;
      default:      Child = Home;
    }

    return (
      <div>
        <h1>App</h1>
        <ul>
          <li><a href="#/about">About</a></li>
          <li><a href="#/inbox">Inbox</a></li>
        </ul>
        <Child/>
      </div>
    )
  }
})

React.render(<App />, document.body)

根据上面的方法,如果在About页面中还有其他子页面,那么case的状况就会变多,并且这个嵌套的结构也会变得更加的复杂。我们再来看一下ReactRouter解决这种路径定向的方式:

使用ReactRouter

import React from 'react'
import { render } from 'react-dom'

// 首先我们需要导入一些组件...
import { Router, Route, Link } from 'react-router'

// 然后我们从应用中删除一堆代码和
// 增加一些 <Link> 元素...
const App = React.createClass({
  render() {
    return (
      <div>
        <h1>App</h1>
        {/* 把 <a> 变成 <Link> */}
        <ul>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/inbox">Inbox</Link></li>
        </ul>
        //这里我们注意一下,this.props.children指的该组件的所有子节点
        {/*
          接着用 `this.props.children` 替换 `<Child>`
          router 会帮我们找到这个 children
        */}
        {this.props.children}
      </div>
    )
  }
})

// 最后,我们用一些 <Route> 来渲染 <Router>。
// 这些就是路由提供的我们想要的东西。
React.render((
  <Router>
    <Route path="/" component={App}>
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox} />
    </Route>
  </Router>
), document.body)

这里面Router组件是个容器,里面的Route组件才是关键,Route组件包含path和component两个参数,path是路径,component是我们要渲染的页面,所有的Route组件组成了一个树形结构,Router会先加载外层的Route组件再加载内层的Router组件。所以当我们的路径是/about,最后渲染出的结构就是:

<App>
  <about />
</App>

这个时候,如果about页面如果还有子页面,我们就可以把子组件包裹在about页面中:

React.render((
  <Router>
    <Route path="/" component={App}>
      <Route path="about" component={About}>
          <Route path="messages/:id" component={Message} />
      </ Route>
      <Route path="inbox" component={Inbox} />
    </Route>
  </Router>
), document.body)

现在访问 URL about/messages/Jkei3c32 将会匹配到一个新的路由,并且它成功指向了 App -> About -> Message 这个 UI 的分支。
仔细观察一下message组件,它后面还跟了一个/:id的字样,这就是我们这个页面接受某个id,就像我们访问CDN的个人博客,就需要带一个个人独有的id,没毛病,这就是URL中的参数。
为了从服务器获取 message 数据,我们首先需要知道它的信息。当渲染组件时,React Router 会自动向 Route 组件中注入一些有用的信息,尤其是路径中动态部分的参数。

const Message = React.createClass({

  componentDidMount() {
    // 来自于路径 `/about/messages/:id`
    const id = this.props.params.id

    fetchMessage(id, function (err, message) {
      this.setState({ message: message })
    })
  },

  // ...

})

3.路由匹配

路由匹配原理

Router会根据URL路径按照深度优先遍历自上而下一层层找相应组件,如果没有那些乱七八糟的通配符,路由的匹配相当简单,顺着路径在对应的组件下面找符合路径的子组件,这个真的没啥好说的。

通配符

通配符匹配规则

(1) :paramName
:paramName匹配URL的一个部分,直到遇到下一个/、?、#为止。这个路径参数可以通过this.props.params.paramName取出。
(2) ()
()表示URL的这个部分是可选的。
(3) *
*匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式。
(4) **
** 匹配任意字符,直到下一个/、?、#为止。匹配方式是贪婪模式。

通配符匹配实例

<Route path="/hello/:name">
// 匹配 /hello/michael
// 匹配 /hello/ryan

<Route path="/hello(/:name)">
// 匹配 /hello
// 匹配 /hello/michael
// 匹配 /hello/ryan

<Route path="/files/*.*">
// 匹配 /files/hello.jpg
// 匹配 /files/hello.html

<Route path="/files/*">
// 匹配 /files/ 
// 匹配 /files/a
// 匹配 /files/a/b

<Route path="/**/*.jpg">
// 匹配 /files/hello.jpg
// 匹配 /files/path/to/file.jpg

注意优先级

路由匹配规则是从上到下执行,一旦发现匹配,就不再其余的规则了。

//先匹配第一个,第二个就不会匹配了
<Route path="/comments" ... />
<Route path="/comments" ... />

//同样,为了防止以下这种情况,我们一般把带参数的放在底部
<Router>
  <Route path="/:userName/:id" component={UserPage}/>
  <Route path="/about/me" component={About}/>
</Router>

4.history

对于history这里的解释我一直很困惑:React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
前面的实例命名没有写明history,不还是能根据url进行跳转吗?其实Router组件在不注明history参数时,默认是history={hashHistory},在reactRouter中共有3种history:

hashHistory

import { hashHistory } from 'react-router'

render(
  <Router history={hashHistory} routes={routes} />,
  document.getElementById('app')
)

当然你也可以不配置,hashHistory的作用就是监听URL中#后的值的变化,就像前面的例子,对应的URL应该是这种形式: ‘http://example.com/#/some/path‘。同时history会把历史网页push进内存,这样我们就可以实现回退,跳转等操作。

browserHistory

import { browserHistory } from 'react-router'

render(
  <Router history={browserHistory} routes={routes} />,
  document.getElementById('app')
)

如果设为browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径’example.com/some/path’,背后调用的是浏览器的History API。所以我们建议的history应该是browerHistory。

createMemoryHistory

Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。

const history = createMemoryHistory(location)

关于history,ReactRouterV4已经有了非常大的改动,这个我们会在V4版本学习笔记中在在探讨。

看最上面的实例我们会发现我们似乎漏掉了一个Home页面,Home页面就是我们在URL中只有’/’加载的App下的默认组件,那么我们如何使Home显示呢?
{this.props.children || }这种写法实现的就是在this.props.children是undefined的情况下(即’/’)加载Home组件,但是因为Home和About,Inbox是同级组件,所以这种结构并不太好。

IndexRoute

IndexRoute就是解决这个问题,显式指定Home是根路由的子组件,即指定默认情况下加载的子组件。

<Router>
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="accounts" component={Accounts}/>
    <Route path="statements" component={Statements}/>
  </Route>
</Router>

现在,用户访问/的时候,加载的组件结构如下:

<App>
  <Home/>
</App>

Link的作用类似于a标签,生成一个链接,用户点击跳转。

<ul>
  <li><Link to="/about">About</Link></li>
  <li><Link to="/inbox">Inbox</Link></li>
</ul>

样式:

<Link to="/about" activeStyle={{color: 'red'}}>About</Link>
<Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link>

或者

<Link to="/about" activeClassName="active">About</Link>
<Link to="/repos" activeClassName="active">Repos</Link>

在Router组件之外,导航到路由页面,可以使用浏览器的History API,像下面这样写。

import { browserHistory } from 'react-router';
browserHistory.push('/some/path');

IndexLink是我们在链接到根路由/时使用的组件,为什么要使用IndexLink,因为对于根路由,我们设置样式是就会总匹配,所以我们就需要一个只匹配根路由样式的组件。

<IndexLink to="/" activeClassName="active">
  Home
</IndexLink>

6.小结

本来是想一口气把ReactRouterV2的知识都学完的,包括之后的ReactRouter高级用法,但是我看了一下高级用法,虽然每小节内容不多,但是却需要花些功夫去理解实践,因此,本篇的内容写到这里,再写也显得冗长。想看更多的直接去看ReactRouter中文文档
另外,ReactRouterV4早就更新了,里面有非常多的改动,因此,写完ReactRouterV2之后,我们会再去研究ReactRouterV4的内容。

 类似资料: