Skip to content

React-Router学习笔记1 #1

@AlvinYuXT

Description

@AlvinYuXT

前言

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方法就是添加浏览器地址栏变化的监听操作。有三种途径会改变浏览器地址栏,分别是:

  1. 浏览器前进/后退
  2. a标签
  3. js逻辑跳转

针对第一种情况,history库中通过全局监听popstate方法解决,针后面两种情况调用history的pushState和replaceState方法完成处理。在监听到url改变后触发初始化时传递进来的回调函数。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions