原文链接:https://react-guide.github.io...

react-router是一个基于react的路由库,它可以让你向应用中快速的添加视图数据流,同时保持页面与URL之间的同步。

路由配置

1. 不使用React-Router的情形

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

const About = React.createClass({...})
const Inbox = React.createClass({...})
const Home = React.createClass({...})

const App = React.createClass({
    getInitialState() {
        return {
            route: window.location.hash.substr(1)
        }
    },
    componentDidMount() {
        window.addEventListener('hashchage', ()=> {
            this.setState({
                route: window.location.hash.substr(1)
            })
        })
    },
    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)

如上,当URL的hash部分(指的是#后的部分)变化后,<App>会根据this.state.route来渲染不同的<Child>。现在看起来很直观,但是当你的路由结构复杂,项目变的比较庞大的时候,这种方法可能就不太适合了。
我们看下在使用react-router后的编码结构:

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

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

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)

看上面代码我们除去了对hash路由的判断,取而代之的是通过react-router控制了视图的显示。在内部,router会将你树级嵌套格式的<Route>转变成路由配置。我们也可以通过普通对象的方式来替代路由配置:

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

2. 获取URL参数

当渲染组件时,React Router会自动向Route组件中注入一些有用的信息,尤其是路径中动态部分的参数。

const Message = React.createClass({

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

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

  // ...

})

3. 路由配置

路由配置是一组指令,用来告诉router如何匹配URL以及匹配后如何执行代码。

import React from 'react'
import { Router, Route, Link } from 'react-router'

const App = React.createClass({
  render() {
    return (
      <div>
        <h1>App</h1>
        <ul>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/inbox">Inbox</Link></li>
        </ul>
        {this.props.children}
      </div>
    )
  }
})

const About = React.createClass({
  render() {
    return <h3>About</h3>
  }
})

const Inbox = React.createClass({
  render() {
    return (
      <div>
        <h2>Inbox</h2>
        {this.props.children || "Welcome to your Inbox"}
      </div>
    )
  }
})

const Message = React.createClass({
  render() {
    return <h3>Message {this.props.params.id}</h3>
  }
})

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

通过上面的配置,可以看到路由是怎么渲染的:

URL 组件
/ App
/about App->About
/inbox App->Inbox
/inbox/messages/:id App->Inbox->Message

4. 添加首页

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

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)

此时,App的render中的this.props.children会将是<Dashboard>这个元素。

URL 组件
/ App->Dashboard
/about App->About
/inbox App->Inbox
/inbox/messages/:id App->Inbox->Message

5. 让UI从URL中解耦出来

如果我们可以将/inbox从/inbox/messages/:id中去除,并且能够让Message嵌套在App-Inbox。那我们可以通过绝对路径实现。

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

绝对路径可能在动态路由中无法使用

6. 兼容旧的URL

如果改为/inbox/messages/5,这样都会匹配不到路径,会返回一个错误页面,我们可以通过<Redirect>使URL重新正常工作。

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)

现在,当有人惦记/inbox/message/5这个链接,他们会自动跳转到/message/5。

7. 替换的配置方式

因为route一般被嵌套使用,所以使用JSX这种天然具有简洁嵌套型语法的结构来描它们的关系非常方便。我们也可以使用原生route数组对象。

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

路由匹配原理

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

1. 嵌套关系
2. 路径语法
3. 优先级

1. 嵌套关系

React Router使用路由嵌套的概念让你定义view的嵌套集合,当一个URL调用时,整个路由集合中(匹配的部分)都会被渲染。嵌套路由被描述成一种属性结构,Reat-Router会深度优先遍历整个路由配置来寻找一个给定的URL相匹配的路由。

2. 路径语法

路由路径是匹配一个URL的一个字符串模式,大部分的路由路径都可以直接按照字面量理解,但是以下有几个特殊的符号:

1. :paramName -- 匹配一段位于/, ?或者#之后的URL.命中的部分将被作为一个参数。
2. () -- 在它内部的内容被认为是可选的
3. * --匹配任意字符串直到命中下一个字符或者整个URL的末尾,并创建要给splat参数。

例子:

<Route path="/hello/:name">         // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)">       // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*">           // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg

3. 优先级

路由算法会根据定义的顺序自顶向下匹配路由,因此,当你拥有两个兄弟路由节点匹配时,你必须确认前一个路由不会匹配后一个路由中的路径。

Histories

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

1. borwserHistory
2. hashHistory
3. createMemoryHistory
import {browserHistory} from 'react-router'
render(
  <Router history={browserHistory} routes={routes} />,
  document.getElementById('app')
)

1. browserHistory

Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path这样真实的 URL 。

2. hashHistory

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

默认路由(IndexRoute)与 IndexLink

1. 默认路由

首先,我们看下不使用默认路由的情形:

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

当用户访问/时,App组件被渲染,但组件内的子元素却没有,App内部的this.props.children为undefined。你可以简单的使用{this.props.chidlren || ''}来渲染默认组件。
但现在,Home 无法参与到比如 onEnter hook 这些路由机制中来。 在 Home 的位置,渲染的是 Accounts 和 Statements。 由此,router 允许你使用 IndexRoute ,以使 Home 作为最高层级的路由出现.

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

现在 App 能够渲染 {this.props.children} 了, 我们也有了一个最高层级的路由,使 Home 可以参与进来。

2. Index Links

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

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

动态路由

对于大型应用来说,一个首当其冲的问题就是所需加载的 JavaScript 的大小。程序应当只加载当前渲染页所需的 JavaScript。有些开发者将这种方式称之为“代码分拆” —— 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。
路由是个非常适于做代码分拆的地方:它的责任就是配置好每个 view。
React Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。在首次加载包中你只需要有一个路径定义,路由会自动解析剩下的路径。
Route 可以定义 getChildRoutes,getIndexRoute 和 getComponents 这几个函数。它们都是异步执行,并且只有在需要时才被调用。我们将这种方式称之为 “逐渐匹配”。 React Router 会逐渐的匹配 URL 并只加载该 URL 对应页面所需的路径配置和组件。


fsrookie
2.9k 声望256 粉丝

目前很多文章都是摘抄记录其他教程。见谅。


引用和评论

0 条评论