React-Router v4 入门

React-Router 新发布了 4.0 正式版本,此版本和之前的版本相比,有比较大的变动,把之前的版本抛到脑后,来重新看看最新 React-Router 4.0

HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from 'react'
import {Route, BrowserRouter, Link} from 'react-router-dom'
const Home = ()=>(
<div>
<h2>Home page, Welcome!</h2>
</div>
)
const About = ()=>(
<div>
<h2>I'm Michael Cai, This is my story!</h2>
</div>
)
const BasicApp = ()=>(
<BrowserRouter>
<div>
<ul>
<li><Link to="/">Index</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/render">Render</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/render" render={()=>{return <h3>Route render without Component!</h3>}}/>
</div>
</BrowserRouter>
)

上面的代码定义了一个最简单的使用 React-Router 管理路由的应用程序,Router 组件是一个容器,Router 下面可以放任意标签,它像是 redux 中的 provider 。真正的路由路径通过 Route 来定义。Link 标签可以理解为 a 标签,点击后会改变浏览器 Url 路径,通过 Route 标签来捕获 url 并返回对应的 component 属性中定义的组件。

path 为 “/“ 的路由中有一个 exact 关键字,这个关键字是将 “/“ 做唯一匹配,否则 “/“ 和 “/xxx” 都会匹配到 path 为 “/“ 的路由,指定 exact 后,”/page1” 就不会再匹配到 “/“ 了。

React Router V4 管理多个 Repository,代码库包括:

  • react-router React Router 核心
  • react-router-dom 用于 DOM 绑定的 React Router
  • react-router-native 用于 React Native 的 React Router
  • react-router-redux React Router 和 Redux 的集成
  • react-router-config 静态路由配置

Route

Route 组件可以放置到任何地方,比如放到一个自定义的 Component 组件中。

Topics.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const Topics = ({match, history})=> {
console.log(match);
// {path: "/topics", url: "/topics", isExact: false, params: Object}
console.log(history);
// {function: push()...}
return (
<div>
<h2>TopicList</h2>
<ul>
<li><Link to={`${match.url}/rendering`}>ReactRendering</Link></li>
<li><Link to={`${match.url}/components`}>Component</Link></li>
<li><Link to={`${match.url}/props-v-state`}>PropsState</Link></li>
<div onClick={()=> { history.push(`${match.url}/rendering`); }}>PushTo ReactRendering</div>
</ul>
<Route path={`${match.url}/:topicId`} component={Topic}/>
<Route exact path={match.url} render={() => (<h3>No Topic Selected!</h3>)}/>
</div>
)
}
let router = (
<BrowserRouter>
// ...
<Route path="/topics" component={Topics}/>
</BrowserRouter>)

上面的 Topics 组件包含了两个部分:

  • 3个直接导航到 topics/:topicId 的链接和1个使用 onClick() 点击跳转到 topics/:topicId 的链接
  • 定义了 Topic 组件和默认组件,当 url 符合 topics/:topicId 规则的时候,就会渲染 Topic 组件。

Topic.js

1
2
3
4
5
6
7
8
9
10
11
class Topic extends React.Component {
render() {
console.log(this.props.match);
// {path: "/topics/:topicId", url: "/topics/rendering", isExact: true, params: {topicId:"rendering"}}
console.log(this.props);
// {history, location, match}
return (<div>
<h3>{this.props.match.params.topicId}</h3>
</div>)
}
}

Topic 组件中直接显示路由中的 topicId

对于 Topic.js ,也可以定义成如下:

1
2
3
4
5
const Topic = ({match})=>(
<div>
<h3>{match.params.topicId}</h3>
</div>
)

match

在上面 Topic 组件定义中,不管是使用继承 React.Component 的方式还是 使用函数的形式定义的组件,都使用了 match 对象。组件中的 match 对象包含了 如何与 URL 匹配的信息。match 对象包含以下属性:

  • params -( object 类型)即路径参数,通过解析URL中动态的部分获得的键值对。
  • isExact - 当为 true 时,整个 URL 需要匹配。
  • path -( string 类型)用来做匹配的路径格式。比如上面的 /topics/:topicId ,在需要嵌套 <Route> 的时候用到。
  • url -( string 类型)URL 匹配的部分,是真实的 url 路径,在需要嵌套 <Link> 的时候会用到。

可以在以下地方获取 match 对象:

  • Route component 中,以 this.props.match 方式。
  • Route render 中,以 ({ match }) => () 方式。
  • Route children 中,以 ({ match }) => () 方式。
  • withRouter 中,以 this.props.match 方式。
  • matchPath() 的返回值

当一个 Route 没有 path 时,它会匹配一切路径,往往会匹配到最近的父级。在 withRouter 里也是一样的。

match_api

history

historyReact-Router 的两大重要依赖之一(除去 React 本身),在不同的 Javascript 环境中,history 以多种形式实现了对于 session 历史的管理。history 有如下几种:

  • browser history : history 在 DOM 上的实现,常使用于支持 HTML5 history API 的浏览器端。
  • hash history : history 在 DOM 上的实现,经常使用于旧版本浏览器端。
  • memory history : 一种存储于内存的 history 实现,经常用于测试或是非 DOM 环境(例如 React Native)。

history 对象属性和方法:

  • length -( number 类型)指的是 history 堆栈的数量。
  • action -( string 类型)指的是当前的动作(action),例如 PUSH,REPLACE 以及 POP 。
  • location -( object类型)是指当前的位置(location),location 会具有如下属性:
    • pathname -( string 类型)URL路径。
    • search -( string 类型)URL中的查询字符串(query string)。
    • hash -( string 类型)URL的 hash 分段。
    • state -( string 类型)是指 location 中的状态,例如在 push(path, state) 时,state会描述什么时候 location 被放置到堆栈中等信息。这个 state 只会出现在 browser history 和 memory history 的环境里。
  • push(path, [state]) -( function 类型)在 hisotry 堆栈顶加入一个新的条目。
  • replace(path, [state]) -( function 类型)替换在 history 堆栈中的当前条目。
  • go(n) -( function 类型)将 history 对战中的指针向前移动 n 。
  • goBack() -( function 类型)等同于 go(-1) 。
  • goForward() -( function 类型)等同于 go(1) 。
  • block(prompt) -( function 类型)阻止跳转,(参照 history 文档)。

history 对象是可变的,因此通常我们从 <Route> 的 props 里来获取 location ,而不是从 history.location 直接获取。这样做可以保证 React 在生命周期中的钩子函数正常执行,例如以下代码:

1
2
3
4
5
6
7
8
9
10
class Topic extends React.Component {
componentWillReceiveProps(nextProps) {
// locationChanged 变量为 true
const locationChanged = nextProps.location !== this.props.location
// 不正确,locationChanged 变量会永远为 false ,因为 history 是可变的(mutable)。
const locationChanged = nextProps.history.location !== this.props.history.location
}
}
<Route component={Topic}/>

比如上面的 Topic 组件,当从 components 切换到 rendering 时,this.props:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{ match:
{ path: '/topics/:topicId',
url: '/topics/components',
isExact: true,
params: { topicId: 'components' } },
location:
{ pathname: '/topics/components',
search: '',
hash: '',
key: 't1tpk6' },
history:
{ length: 29,
action: 'PUSH',
location:
{ pathname: '/topics/rendering',
search: '',
hash: '',
key: 'r4987o'
}
}
}

nextProps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{ match:
{ path: '/topics/:topicId',
url: '/topics/rendering',
isExact: true,
params: { topicId: 'rendering' } },
location:
{ pathname: '/topics/rendering',
search: '',
hash: '',
key: 'r4987o' },
history:
{ length: 29,
action: 'PUSH',
location:
{ pathname: '/topics/rendering',
search: '',
hash: '',
key: 'r4987o'
}
}
}

location

一个 location 对象结构如下

1
2
3
4
5
6
7
8
9
{
key: 'ac3df4', // 在使用 hashHistory 时,没有 key
pathname: '/somewhere'
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: true
}
}

可以使用以下几种方式来获取 location 对象:

  • Route component 中,以 this.props.location 的方式获取,
  • Route render 中,以 ({ location }) => () 的方式获取,
  • Route children 中,以 ({ location }) => () 的方式获取,
  • withRouter 中,以 this.props.location 的方式获取。

location 对象不会发生改变,因此你可以在生命周期的钩子函数中使用 location 对象来查看当前页面的位置是否发生改变,这种技巧在获取远程数据以及使用动画时非常有用。

1
2
3
4
5
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
// 已经跳转了!
}
}

可以在不同环境中使用 location

通常情况下,只需要给一个字符串当做 location ,但是,当需要添加一些 location 的状态时,可以对象的形式使用 location 。并且如果需要多个 UI ,而这些 UI 取决于历史时,例如弹出框(modal),使用 location 对象会有很大帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 通常你只需要这样使用 location
<Link to="/somewhere"/>
// 但是你同样可以这么用
const location = {
pathname: '/somewhere'
state: { fromDashboard: true }
}
<Link to={location}/>
<Redirect to={location}/>
history.push(location)
history.replace(location)

location_api

withRouter

matchPath