前言
React Router V4版本已发布,本文以V4版本为主要的源码研究版本。V4中包含了多个package,如下所示:
- react-router
- react-router-dom
- react-router-native
- react-router-redux
本文主要针对react-router-dom进行源码分析。
demo
在进行源码分析之前,我们首先需要知道React Router如何使用,代码如下:
import { BrowserRouter, Link, Route } from 'react-router-dom'
ReactDOM.render((
<BrowserRouter>
<App/>
</BrowserRouter>
), el)
const App = () => (
<div>
<nav>
<Link to="/dashboard">Dashboard</Link>
</nav>
<div>
<Route path="/dashboard" component={Dashboard}/>
</div>
</div>
)
分析
在上面的代码中我们看到主要是用到了React Router的三个组件BrowserRouter、Route、和Link,三者具体的干嘛的就自相查看文档吧。这里要说明一下BrowserRouter是默认使用historyAPI的。
BrowserRouter源码
class BrowserRouter extends React.Component {
static propTypes = {
basename: PropTypes.string,
forceRefresh: PropTypes.bool,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number,
children: PropTypes.node
}
history = createHistory(this.props)
componentWillMount() {
warning(
!this.props.history,
'<BrowserRouter> ignores the history prop. To use a custom history, ' +
'use `import { Router }` instead of `import { BrowserRouter as Router }`.'
)
}
render() {
return <Router history={this.history} children={this.props.children}/>
}
}
export default BrowserRouter
这段代码非常好懂,就是初始化了history实例,并将history实例传入到Router组件中
Route解析
由于Router不知道Link以及Route组件在什么层次结构会被引用,所以为了将hitory等实例传递到Link和Route组件中,使用到了React的Context,通过getChildContext方法将对象往子组件中传递,这样只要是Router的子组件都能够获取到return的对象。而在子组件中,只要使用this.context.router就能过访问到router实例。
getChildContext() {
return {
router: {
...this.context.router,
history: this.props.history,
route: {
location: this.props.history.location,
match: this.state.match
}
}
}
}
在思考浏览器路由变化的时候,React Router是如何更新组件的这个问题上我一开始没想明白,直到我看到了这句
state = {
match: this.computeMatch(this.props.history.location.pathname)
}
computeMatch(pathname) {
const { path = '/', exact = false, strict = false, sensitive = false } = options
const { re, keys } = compilePath(path, { end: exact, strict, sensitive })
const match = re.exec(pathname)
if (!match)
return null
const [ url, ...values ] = match
const isExact = pathname === url
if (exact && !isExact)
return null
return {
path, // the path pattern used to match
url: path === '/' && url === '' ? '/' : url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params: keys.reduce((memo, key, index) => {
memo[key.name] = values[index]
return memo
}, {})
}
}
render() {
const { match } = this.state
const { children, component, render } = this.props
const { history, route, staticContext } = this.context.router
const location = this.props.location || route.location
const props = { match, location, history, staticContext }
if (component)
return match ? React.createElement(component, props) : null
if (render)
return match ? render(props) : null
if (typeof children === 'function')
return children(props)
if (children && !isEmptyChildren(children))
return React.Children.only(children)
return null
}
通过this.computeMatch方法算出当前路由是否与特定组件匹配,然后通过setState就会自动更新,实在是厉害。
computeMatch方法这么个处理方式基本上就宣布,只要url变化组件就一定更新。
到这里你可能会问那么如何监听浏览器路由的变化呢?接下来就介绍这段代码
componentWillMount() {
const { children, history } = this.props
// 保证Router下之后一个子节点
invariant(
children == null || React.Children.count(children) === 1,
'A <Router> may have only one child element'
)
// 调用hitory API完成注册
this.unlisten = history.listen(() => {
this.setState({
match: this.computeMatch(history.location.pathname)
})
})
}
这里用到了ReactTraining的history库,listen方法就是添加浏览器地址栏变化的监听操作。有三种途径会改变浏览器地址栏,分别是:
- 浏览器前进/后退
- a标签
- js逻辑跳转
针对第一种情况,history库中通过全局监听popstate方法解决,针后面两种情况调用history的pushState和replaceState方法完成处理。在监听到url改变后触发初始化时传递进来的回调函数。
前言
React Router V4版本已发布,本文以V4版本为主要的源码研究版本。V4中包含了多个package,如下所示:
本文主要针对react-router-dom进行源码分析。
demo
在进行源码分析之前,我们首先需要知道React Router如何使用,代码如下:
分析
在上面的代码中我们看到主要是用到了React Router的三个组件BrowserRouter、Route、和Link,三者具体的干嘛的就自相查看文档吧。这里要说明一下BrowserRouter是默认使用historyAPI的。
BrowserRouter源码
这段代码非常好懂,就是初始化了history实例,并将history实例传入到Router组件中
Route解析
由于Router不知道Link以及Route组件在什么层次结构会被引用,所以为了将hitory等实例传递到Link和Route组件中,使用到了React的Context,通过getChildContext方法将对象往子组件中传递,这样只要是Router的子组件都能够获取到return的对象。而在子组件中,只要使用this.context.router就能过访问到router实例。
在思考浏览器路由变化的时候,React Router是如何更新组件的这个问题上我一开始没想明白,直到我看到了这句
通过this.computeMatch方法算出当前路由是否与特定组件匹配,然后通过setState就会自动更新,实在是厉害。
computeMatch方法这么个处理方式基本上就宣布,只要url变化组件就一定更新。
到这里你可能会问那么如何监听浏览器路由的变化呢?接下来就介绍这段代码
这里用到了ReactTraining的history库,listen方法就是添加浏览器地址栏变化的监听操作。有三种途径会改变浏览器地址栏,分别是:
针对第一种情况,history库中通过全局监听popstate方法解决,针后面两种情况调用history的pushState和replaceState方法完成处理。在监听到url改变后触发初始化时传递进来的回调函数。