> For the complete documentation index, see [llms.txt](https://lambda68.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lambda68.gitbook.io/blog/react/javascript.md).

# 页面路由——React-Router

## React-Router

### 简介

React Router 是一个基于 React 之上的强大路由库，它可以让你向应用中快速地添加视图和数据流，同时保持页面与 URL 之间的同步。

#### 使用 React Router

```jsx
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` 替换 `<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)
```

如果你不熟悉 JSX，也可以用普通对象来替代。

```js
const routes = {
    path: '/',
    component: App,
    childRoutes: [
        {path: 'about', component: About},
        {path: 'inbox', component: Inbox}
    ]
}
React.render(<Router routes={routes} />, document.body)
```

#### 获取 URL 参数

为了从服务器获取 message 数据，我们首先需要知道它的信息。当渲染组件时，React Router 会自动向 Route 组件中注入一些有用的信息，尤其是路径中动态部分的参数。

```jsx
const Message = React.createClass({

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

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

也可以通过 query 字符串来访问参数。比如访问 `/foo?bar=baz`可以通过访问 `this.props.location.query.bar` 从 Route 组件中获取 `'baz'`的值

### 基础

![YzAfw8.png](https://s1.ax1x.com/2020/05/24/YzAfw8.png)

#### 添加首页

当URL为`/`时，我们想渲染一个在 `App`中的组件。不过此时，App 的 render中的`this.prop.children`还是undefined。这种情况下我们可以使用 `IndexRoute`来设置一个默认页面。

```jsx
import { IndexRoute } from 'react-router'

const Dashboard = React.createClass({
  render() {
    return <div>Welcome to the app!</div>
  }
})

React.render((
  <Router>
    <Route path="/" component={App}>
      {/* 当 url 为 / 时渲染 Dashboard */}
      <IndexRoute component={Dashboard} />
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>
), document.body)
```

#### 让 UI 从 URL 中解耦出来

通过在定义 path 中使用 `/messages/:id` 替换 `messages/:id` 在多层前套路由中使用绝对路径的能力让我们对 URL 拥有绝对的掌握。我们无需在 URL 中添加更多的层级。

#### 兼容旧的 URL

使用 `<Redirect>` 使URL重定向。

```jsx
import { Redirect } from 'react-router'

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

        {/* 跳转 /inbox/messages/:id 到 /messages/:id */}
        <Redirect from="messages/:id" to="/messages/:id" />
      </Route>
    </Route>
  </Router>
), document.body)
```

#### 进入和离开 Hook

Route 可以定义 `onEnter` 和 `onLeave` 两个 hook，这些 hook 会在页面跳转确认时触发一次。这些 hook 对于一些情况非常的有用，例如权限验证或者在路由跳转前将一些数据持久化保存起来。

在路由跳转过程中

* `onLeave` 会在所有将离开的路由中触发，从最下层的路由开始直到最外层路由结束。
* `onEnter` 会在将进入的路由时触发。

#### 替换配置方式

如果你不想使用 JSX，也可以直接使用原生的 route 数组对象。

```jsx
const routeConfig = [
  { path: '/',
    component: App,
    indexRoute: { component: Dashboard },
    childRoutes: [
      { path: 'about', component: About },
      { path: 'inbox',
        component: Inbox,
        childRoutes: [
          { path: '/messages/:id', component: Message },
          { path: 'messages/:id',
            onEnter: function (nextState, replaceState) {
              replaceState(null, '/messages/' + nextState.params.id)
            }
          }
        ]
      }
    ]
  }
]

React.render(<Router routes={routeConfig} />, document.body)
```

### 路由匹配原理

路由拥有三个属性来决定是否匹配一个URL

* 嵌套关系
* 路径语法
* 优先级

#### 嵌套关系

当一个给定的 URL 被调用时，整个集合中(命中的部分)都会被渲染。嵌套路由被描述成一种树形结构

#### 路径语法

匹配一个(或一部分)URL的一个字符串模式。大部分的路由路径都可以直接按照字面量理解，除了下边的特殊符号

* `:paramName` - 匹配一段位于 `/`、`?`或`#`之后的URL
* `()` - 在它内的内容被认为是可选的
* `*` - 匹配任意字符直到命中下一个字符或者整个 URL 的末尾

#### 优先级

路由算法会根据定义的顺序自顶向下匹配路由

### Histories

React Router是建立在 history 上的。简而言之，一个 history 知道如何去监听浏览器地址栏的变化，并解析这个 URL 转化为 location 对象，然后 router 使用它匹配到路由，最后正确的渲染页面。

* browserHistory
* hashHistory
* createMemoryHistory

#### 使用

```jsx
import { browserHistory } from 'react-router'

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

#### browserHistory

他使用浏览器中 History API 用于处理 URL

[**服务器配置**](https://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html)

服务器需要做好处理 URL 的准备。处理应用启动最初的/这样的请求是没问题的，但当用户来回跳转并在`/account/123`刷新时，服务器就会收到来自 `account/123`的请求，这时需要处理这个 URL 并响应对应的 JavaScript 代码。

#### hashHistory

Hash History 使用 URL 中的 hash `#` 部分去创建形如`example.com/#/some/path` 的路由。

**对比**

Hash History 不需要服务器任何配置就可以运行，但是我们不推荐在实际线上环境中使用，因为每一个 web 应用都应该渴望使用 browserHistory。

**传递参数**

当一个 History 通过应用程序中的 push 或 replace跳转时，他可以在新的 location中存储 location state而不显示在 URL 中，这就像是在一个 HTML 中 post 的表单数据。

#### createMemoryHistory

Memory History 不会在地址栏被操作或读取。同时它也非常适合测试和其他渲染环境

和另外两种 history 的一点不同是必须创建它，这种方式便于测试。

```jsx
const history = createMemoryHistory(location)
```

#### 使用

```jsx
import React from 'react'
import { render } from 'react-dom'
import { browserHistory, Router, Route, IndexRoute } from 'react-router'

import App from '../components/App'
import Home from '../components/Home'
import About from '../components/About'
import Features from '../components/Features'

render(
  <Router history={browserHistory}>
    <Route path='/' component={App}>
      <IndexRoute component={Home} />
      <Route path='about' component={About} />
      <Route path='features' component={Features} />
    </Route>
  </Router>,
  document.getElementById('app')
)
```

#### 默认路由 `(IndexRoute)` 与 `IndexLink`

**默认路由 `(IndexRoute)`**

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

**Index Links**

如果你在这个 app 中使用 `<Link to="/">Home</Link>` , 它会一直处于激活状态，因为所有的 URL 的开头都是 `/` 。 这确实是个问题，因为我们仅仅希望在 `Home` 被渲染后，激活并链接到它。

如果需要在 `Home` 路由被渲染后才激活的指向 `/` 的链接，请使用 `<IndexLink to="/">Home</IndexLink>`

### 高级用法

![YzAqO0.png](https://s1.ax1x.com/2020/05/24/YzAqO0.png)

#### 动态路由

React Router 适用于小型网站，也可以支持大型网站。

对于大型应用来说，一个首当其冲的问题就是所需加载的 JavaScript 的大小。程序应当只加载渲染页所需的 JavaScript。

路由是个非常适于做代码分拆的地方：他的责任就是配置好每个 view

React Router 里的路径匹配以及组件加载都是异步完成，不仅允许你延迟加载组件，并且可以延迟加载路由配置。在首次加载包中你只需要有一个路径定义，路由会自动解析剩下的路径。

Route 可以定义 `getChildRoutes`，`getIndexRoute`和`getComponents`这几个函数。他们都是异步执行，并且只有在需要的时候才被调用。称之为“逐渐匹配”。

匹配 URL 并只加载该 URL 对应页面所需的路径配置和组件。

```jsx
const CourseRoute = {
  path: 'course/:courseId',

  getChildRoutes(location, callback) {
    require.ensure([], function (require) {
      callback(null, [
        require('./routes/Announcements'),
        require('./routes/Assignments'),
        require('./routes/Grades'),
      ])
    })
  },

  getIndexRoute(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Index'))
    })
  },

  getComponents(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Course'))
    })
  }
}
```

#### 跳转前确认

React Router 它提供一个 `routerWillLeave` 钩子，使得组件可以拦截正在发生的跳转，或在离开 route 前提示用户。

1. `return false` 取消此次跳转
2. `return`返回提示信息，在离开 route前提示用户进行确认。

通过在 route 组件中引入 `Lifecycle` mixin 来安装这钩子

```jsx
import { Lifecycle } from 'react-router'

const Home = React.createClass({

  // 假设 Home 是一个 route 组件，它可能会使用
  // Lifecycle mixin 去获得一个 routerWillLeave 方法。
  mixins: [ Lifecycle ],

  routerWillLeave(nextLocation) {
    if (!this.state.isSaved)
      return 'Your work is not saved! Are you sure you want to leave?'
  },
})
```

#### 服务端渲染

服务端渲染与客户端渲染有些许不同

* 发生错误时发送一个 500 的响应
* 需要重定向时发送一个 30x 的响应
* 在渲染之前获得数据（用 router 帮你完成这点）

为了迎合这一需求，需要在 `<Router />`API 下一层使用

* 使用 `match` 在渲染之前根据 location 匹配 route
* 使用 `RoutingContext` 同步渲染route组件

```jsx
import { renderToString } from 'react-dom/server'
import { match, RoutingContext } from 'react-router'
import routes from './routes'

serve((req, res) => {
  // 注意！这里的 req.url 应该是从初始请求中获得的
  // 完整的 URL 路径，包括查询字符串。
  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (error) {
      res.send(500, error.message)
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
      res.send(200, renderToString(<RoutingContext {...renderProps} />))
    } else {
      res.send(404, 'Not found')
    }
  })
})
```

#### 组件生命周期

```jsx
<Route path="/" component={App}>
  <IndexRoute component={Home}/>
  <Route path="invoices/:invoiceId" component={Invoice}/>
  <Route path="accounts/:accountId" component={Account}/>
</Route>
```

路由切换时，组件生命周期的变化情况

1.当用户打开应用 `'/'` 页面

| 组件      | 生命周期              |
| ------- | ----------------- |
| APP     | componentDidMount |
| Home    | componentDidMount |
| Invoice | N/A               |
| Account | N/A               |

2.当用户从 `'/'`跳转到 `'/invoice/123'`

| 组件      | 生命周期                                          |
| ------- | --------------------------------------------- |
| App     | componentWillReceiveProps, componentDidUpdate |
| Home    | componentWillUnmount                          |
| Invoice | componentDidMount                             |
| Account | N/A                                           |

* App 从 router 中接收到新的 props，所以触发了两个生命周期方法
* Home 不再被渲染，所以它将被移除
* Invoice 首次被挂载

3.当用户从 `/invoice/123` 跳转到 `/invoice/789`

| 组件      | 生命周期                                          |
| ------- | --------------------------------------------- |
| App     | componentWillReceiveProps, componentDidUpdate |
| Home    | N/A                                           |
| Invoice | componentWillReceiveProps, componentDidUpdate |
| Account | N/A                                           |

4.当从 `/invoice/789` 跳转到 `/accounts/123`

| 组件      | 生命周期                                          |
| ------- | --------------------------------------------- |
| App     | componentWillReceiveProps, componentDidUpdate |
| Home    | N/A                                           |
| Invoice | componentWillUnmount                          |
| Account | componentDidMount                             |

**获取数据**

我们在 `Invoice` 组件里实现一个简单的数据获取功能

```jsx
let Invoice = React.createClass({

  getInitialState () {
    return {
      invoice: null
    }
  },

  componentDidMount () {
    // 上面的步骤2，在此初始化数据
    this.fetchInvoice()
  },

  componentDidUpdate (prevProps) {
    // 上面步骤3，通过参数更新数据
    let oldId = prevProps.params.invoiceId
    let newId = this.props.params.invoiceId
    if (newId !== oldId)
      this.fetchInvoice()
  },

  componentWillUnmount () {
    // 上面步骤四，在组件移除前忽略正在进行中的请求
    this.ignoreLastFetch = true
  },

  fetchInvoice () {
    let url = `/api/invoices/${this.props.params.invoiceId}`
    this.request = fetch(url, (err, data) => {
      if (!this.ignoreLastFetch)
        this.setState({ invoice: data.invoice })
    })
  },

  render () {
    return <InvoiceView invoice={this.state.invoice}/>
  }

})
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lambda68.gitbook.io/blog/react/javascript.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
