如果你已经是一个正在开发中的react应用,想要引入更好的管理路由功能。那么,react-router是你最好的选择~
react-router版本现今已经到4.0.0了,而上一个稳定版本还是2.8.1。相信我,如果你的项目中已经在使用react-router之前的版本,那一定要慎重的更新,因为新的版本是一次非常大的改动,如果你要更新,工作量并不小。
这篇文章不讨论版本的变化,只是讨论一下React-router4.0的用法和源码。
源码在这里:https://github.com/ReactTraining/react-router

1.准备

只需要在你的react app中引入一个包
yarn add react-router-dom@next
注:react-router-dom是对react-router的作了一些小升级的库,代码基于react-router

2.使用

我们直接上例子:

import React from 'react'
import {BrowserRouter as Router,Route,Link} from 'react-router-dom' const Basic = () => (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/page1">Page1</Link></li>
<li><Link to="/page2">Page2</Link></li>
</ul> <hr/> <Route exact path="/" component={Home}/>
<Route path="/page1" component={Page1}/>
<Route path="/page2" component={Page2}/>
</div>
</Router>
)

跟之前的版本一样,Router这个组件还是一个容器,但是它的角色变了,4.0的Router下面可以放任意标签了,这意味着使用方式的转变,它更像redux中的provider了。通过上面的例子相信你也可以看到具体的变化。而真正的路由通过Route来定义。Link标签目前看来也没什么变化,依然可以理解为a标签,点击会改变浏览器Url的hash值,通过Route标签来捕获这个url并返回component属性中定义的组件,你可能注意到在为"/"写的路由中有一个exact关键字,这个关键字是将"/"做唯一匹配,否则"/"和"/xxx"都会匹配到path为"/"的路由,制定exact后,"/page1"就不会再匹配到"/"了。如果你不懂,动手试一下~

通过Route路由的组件,可以拿到一个match参数,这个参数是一个对象,其中包含几个数据:

  • isExact:刚才已经说过这个关键字,表示是为作全等匹配
  • params:path中包含的一些额外数据
  • path:Route组件path属性的值
  • url:实际url的hash值

我们来实现一下刚才的Page2组件:

const Page2 = ({ match }) => (
<div>
<h2>Page2</h2>
<ul>
<li>
<Link to={`${match.url}/branch1`}>
branch1
</Link>
</li>
<li>
<Link to={`${match.url}/branch2`}>
branch2
</Link>
</li>
<li>
<Link to={`${match.url}/branch3`}>
branch3
</Link>
</li>
</ul> <Route path={`${match.url}/:branchId`} component={Branch} />
<Route exact path={match.url} render={() => (
<h3>Default Information</h3>
)} />
</div>
) const Branch = ({ match }) => {
console.log(match);
return (
<div>
<h3>{match.params.branchId}</h3>
</div>
)
}

很简单,动手试一试。需要注意的就只有Route的path中冒号":"后的部分相当于通配符,而匹配到的url将会把匹配的部分作为match.param中的属性传递给组件,属性名就是冒号后的字符串。

3.Router标签

细心的朋友肯定注意到了上面的例子中我import的Router是BrowserRouter,这是什么东西呢?如果你用过老版本的react-router,你一定知道history。history是用来兼容不同浏览器或者环境下的历史记录管理的,当我跳转或者点击浏览器的后退按钮时,history就必须记录这些变化,而之前的react-router将history分为三类。

  • hashHistory 老版本浏览器的history
  • browserHistory h5的history
  • memoryHistory node环境下的history,存储在memory中

4.0之前版本的react-router针对三者分别实现了createHashHistory、createBrowserHistory和create MemoryHistory三个方法来创建三种情况下的history,这里就不讨论他们不同的处理方式了,好奇的可以去了解一下~
到了4.0版本,在react-router-dom中直接将这三种history作了内置,于是我们看到了BrowserRouter、HashRouter、MemoryRouter这三种Router,当然,你依然可以使用React-router中的Router,然后自己通过createHistory来创建history来传入。

react-router的history库依然使用的是 https://github.com/ReactTraining/history

4.Route标签

在例子中你可能注意到了Route的几个prop

  • exact: propType.bool
  • path: propType.string
  • component: propType.func
  • render: propType.func

他们都不是必填项,注意,如果path没有赋值,那么此Route就是默认渲染的。
Route的作用就是当url和Route中path属性的值匹配时,就渲染component中的组件或者render中的内容。

当然,Route其实还有几个属性,比如location,strict,chilren 希望你们自己去了解一下。

说到这,那么Route的内部是怎样实现这个机制的呢?不难猜测肯定是用一个匹配的方法来实现的,那么Route是怎么知道url更新了然后进行重新匹配并渲染的呢?

整理一下思路,在一个web
应用中,改变url无非是2种方式,一种是利用超链接进行跳转,另一种是使用浏览器的前进和回退功能。前者的在触发Link的跳转事件之后触发,而后者呢?Route利用的是我们上面说到过的history的listen方法来监听url的变化。为了防止引入新的库,Route的创作者选择了使用html5中的popState事件,只要点击了浏览器的前进或者后退按钮,这个事件就会触发,我们来看一下Route的代码:

class Route extends Component {
static propTypes: {
path: PropTypes.string,
exact: PropTypes.bool,
component: PropTypes.func,
render: PropTypes.func,
} componentWillMount() {
addEventListener("popstate", this.handlePop)
} componentWillUnmount() {
removeEventListener("popstate", this.handlePop)
} handlePop = () => {
this.forceUpdate()
} render() {
const {
path,
exact,
component,
render,
} = this.props //location是一个全局变量
const match = matchPath(location.pathname, { path, exact }) return (
//有趣的是从这里我们可以看出各属性渲染的优先级,component第一
component ? (
match ? React.createElement(component, props) : null
) : render ? ( // render prop is next, only called if there's a match
match ? render(props) : null
) : children ? ( // children come last, always called
typeof children === 'function' ? (
children(props)
) : !Array.isArray(children) || children.length ? ( // Preact defaults to empty children array
React.Children.only(children)
) : (
null
)
) : (
null
)
)
}
}

这里我只贴出了关键代码,如果你使用过React,相信你能看懂,Route在组件将要Mount的时候添加popState事件的监听,每当popState事件触发,就使用forceUpdate强制刷新,从而基于当前的location.pathname进行一次匹配,再根据结果渲染。

PS:现在最新的代码中,Route源码其实是通过componentWillReceiveProps中setState来实现重新渲染的,match属性是作为Route组件的state存在的.

那么这个关键的matchPath方法是怎么实现的呢?
Route引入了一个外部library:path-to-regexp。这个pathToRegexp方法用于返回一个满足要求的正则表达式,举个例子:

let keys = [],keys2=[]
let re = pathToRegexp('/foo/:bar', keys)
//re = /^\/foo\/([^\/]+?)\/?$/i keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }] let re2 = pathToRegexp('/foo/bar', keys2)
//re2 = /^\/foo\/bar(?:\/(?=$))?$/i keys2 = []

关于它的详细信息你可以看这里:https://github.com/pillarjs/path-to-regexp

值得一提的是matchPath方法中对匹配结果作了缓存,如果是已经匹配过的字符串,就不用再进行一次pathToRegexp了。

随后的代码就清晰了:

const match = re.exec(pathname)

if (!match)
return null const [ url, ...values ] = match
const isExact = pathname === url //如果exact为true,需要pathname===url
if (exact && !isExact)
return null return {
path,
url: path === '/' && url === '' ? '/' : url,
isExact,
params: keys.reduce((memo, key, index) => {
memo[key.name] = values[index]
return memo
}, {})
}

5.Link

还记得上面说到的改变url的两种方式吗,我们来说说另一种,Link,看一下它的参数:

static propTypes = {
onClick: PropTypes.func,
target: PropTypes.string,
replace: PropTypes.bool,
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]).isRequired
}

onClick就不说了,target属性就是a标签的target属性,to相当于href。
而replace的意思跳转的链接是否覆盖history中当前的url,若为true,新的url将会覆盖history中的当前值,而不是向其中添加一个新的。

handleClick = (event) => {
if (this.props.onClick)
this.props.onClick(event) if (
!event.defaultPrevented && // 是否阻止了默认事件
event.button === 0 && // 确定是鼠标左键点击
!this.props.target && // 避免打开新窗口的情况
!isModifiedEvent(event) // 无视特殊的key值,是否同时按下了ctrl、shift、alt、meta
) {
event.preventDefault() const { history } = this.context.router
const { replace, to } = this.props if (replace) {
history.replace(to)
} else {
history.push(to)
}
}
}

需要注意的是,history.push和history.replace使用的是pushState方法和replaceState方法。

6.Redirect

我想单独再多说一下Redirect组件,源码很有意思:

class Redirect extends React.Component {
//...省略一部分代码 isStatic() {
return this.context.router && this.context.router.staticContext
} componentWillMount() {
if (this.isStatic())
this.perform()
} componentDidMount() {
if (!this.isStatic())
this.perform()
} perform() {
const { history } = this.context.router
const { push, to } = this.props if (push) {
history.push(to)
} else {
history.replace(to)
}
} render() {
return null
}
}

很容易注意到这个组件并没有UI,render方法return了一个null。很容易产生这样一个疑问,既然没有UI为什么react-router的创造者依然选择将Redirect写成一个组件呢?

我想我们可以从作者口中的"Just Components API"中窥得原因吧。

希望这篇文章可以帮助你更好的创建你的React应用.

文章来自   http://www.jianshu.com/p/27ee7df4ccc1

最新文章

  1. LoadRunner 获取接口请求响应信息
  2. 结合个人经历总结的前端入门方法 (转自https://github.com/qiu-deqing/FE-learning)
  3. CSS第一天总结
  4. Hadoop学习10--常用命令记录帖
  5. android笔记:Notification通知的使用
  6. 【leetcode】365. Water and Jug Problem
  7. POJ 2516 最小费用最大流
  8. 九度OJ 1202 排序 -- 堆排序
  9. Mysql 储存过程以及 python callproc调用
  10. web项目jsp出现The superclass javax.servlet.http.HttpServlet was not found on the Java Build Path错误
  11. Mac状态栏wifi图标一直闪烁重复连接但是网络正常的解决办法
  12. ASP.NET Core 2.2 十八.各种Filter的内部处理机制及执行顺序
  13. 距离放弃python又近了一大步,而然只是第四天
  14. Servlet 起航 文件上传 中文文件名下载
  15. Spring boot多模块(moudle)中的一个注入错误(Unable to start embedded container; nested exception is org)
  16. GCD 之线程死锁
  17. python 引入本地module
  18. Centos6.5下升级Python版本
  19. anu - children
  20. 如何推行Code Review

热门文章

  1. 对OAuth协议的认识
  2. VUE中总的逻辑关系和移动端mint-ui的应用接触
  3. Centos6.10源码部署zabbix-3.2.6
  4. Python调用selenium
  5. react组件生命周期
  6. 咸鱼入门到放弃6--jsp&lt;一&gt;三指令
  7. 4.基于梯度的攻击——MIM
  8. yii2 部分很实用的代码
  9. python3 配置logging日志类
  10. Chapter 2 Basic Elements of JAVA