We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在项目过程中,对 react-router 这个库了解甚少,只是停留在一些基础的用法层面,源码层面的东西还是云里雾里,对它如何运作的原理也是知之甚少,所以趁着最近修 bug 的空档期深入学习一下。
此文基于 react-router 4 进行讲解,如果对 react-router 还不太熟悉的童鞋可以移步 初探 React Router 4.0 和 react-router 的 API 文档地址 先学习一下。
react-router-dom 中包含了 web 端所有路由相关的东西,它基于 react-router 进行了一些包装,所以项目中只需要引入 react-router-dom 即可。react-router 4 中强调万物即组件,其暴露的几乎所有东西都是一个 react 组件,正是因为这种 Just Components 的思想使得开发者更容易在项目中使用路由。 一张图显示出他们之间的关系:
从上图可知,react-router-dom 中的很多元素都是直接从 react-router 中拿过来的,只是在它的基础上进行了一些功能上的扩展。
Router 是创建路由最外面的包裹层组件,有点类似 Provider 的感觉,它的作用是监听路由的变化,从而渲染的页面组件。其源码如下:
class Router extends React.Component { static propTypes = { history: PropTypes.object.isRequired, children: PropTypes.node }; state = { match: this.computeMatch(this.props.history.location.pathname) }; computeMatch(pathname) { return { path: "/", url: "/", params: {}, isExact: pathname === "/" }; } componentWillMount() { const { children, history } = this.props; this.unlisten = history.listen(() => { this.setState({ match: this.computeMatch(history.location.pathname) }); }); } componentWillUnmount() { this.unlisten(); } render() { const { children } = this.props; return children ? React.Children.only(children) : null; } }
源码部分中包含声明接受的 props 数据为 history 对象和要显示的 child 节点内容。并且在组件渲染之前(componentWillMount) 的时候,监听路由( history 是一个记录浏览器记录的一个库)的改变,当 url 发生更改时可以执行传递过去的 setState 回调,当 state 进行更新时,就会重新执行 render 对应的逻辑,从而引发页面的重新渲染。在 Router 组件销毁期间,取消对浏览器的监听。 history 有三种形式,分别是:
import { createBrowserHistory as createHistory } from "history"; 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); render() { return <Router history={this.history} children={this.props.children} />; } }
内容很简单,结合 Router 源码很好理解。HashRouter 和 MemoryRouter 的套路一样,在此不再赘述。
Route 是嵌在 Router 里面的元素,如下:
<Router history={browserHistory}> <Route path="/" component={App}> <Route path="about" component={About}/> <Route path="users" component={Users}> <Route path="/user/:userId" component={User}/> </Route> </Route> </Router>
它的作用是根据 url 进行匹配,如果匹配到当前路由,就展示对应的组件或者内容,否则显示 null。主要源码如下:
class Route extends React.Component { static propTypes = { computedMatch: PropTypes.object, // private, from <Switch> path: PropTypes.string, exact: PropTypes.bool, strict: PropTypes.bool, sensitive: PropTypes.bool, component: PropTypes.func, render: PropTypes.func, children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), location: PropTypes.object }; state = { match: this.computeMatch(this.props, this.context.router) }; computeMatch( { computedMatch, location, path, strict, exact, sensitive }, router ) { if (computedMatch) return computedMatch; // <Switch> already computed the match for us const { route } = router; const pathname = (location || route.location).pathname; return matchPath(pathname, { path, strict, exact, sensitive }, route.match); } componentWillReceiveProps(nextProps, nextContext) { this.setState({ match: this.computeMatch(nextProps, nextContext.router) }); } 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; } }
Route 在 componentWillReceiveProps 这个生命周期通过接受父级组件传来的 router 数据,从而来不断更新自己的 state 数据,进而达到重新渲染组件的效果。 其中 matchPath 这个方法,主要是用 path-to-regexp 这个库来匹配路由参数,并且将已经访问过的路由进行缓存,下次再匹配到这个路由的时候直接返回匹配的数据,其中缓存限制为 10000,具体的逻辑可以看 matchPath 源码,这里不贴出。 其中, strict, exact, sensitive 只是限定了一些匹配的规则,然后在 render 中进行渲染对应的渲染函数,根据传入的渲染规则不同,执行不同的渲染方式。
Link 在浏览器中渲染出来就是一个 a 标签,只是动态赋给 a 一个 href 进行页面跳转,源码如下:
class Link extends React.Component { static propTypes = { onClick: PropTypes.func, target: PropTypes.string, replace: PropTypes.bool, to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, innerRef: PropTypes.oneOfType([PropTypes.string, PropTypes.func]) }; handleClick = event => { if (this.props.onClick) this.props.onClick(event); if ( !event.defaultPrevented && // onClick prevented default event.button === 0 && // ignore everything but left clicks !this.props.target && // let browser handle "target=_blank" etc. !isModifiedEvent(event) // ignore clicks with modifier keys ) { event.preventDefault(); const { history } = this.context.router; const { replace, to } = this.props; if (replace) { history.replace(to); } else { history.push(to); } } }; render() { const { replace, to, innerRef, ...props } = this.props; // eslint-disable-line no-unused-vars const { history } = this.context.router; const location = typeof to === "string" ? createLocation(to, null, null, history.location) : to; const href = history.createHref(location); return ( <a {...props} onClick={this.handleClick} href={href} ref={innerRef} /> ); } }
如上所示:渲染一个 a 标签,并获取一个 href,当点击的时候,触发点击的回调函数,为了防止页面刷新,需要禁掉浏览器的默认行为,所以在 handleClick 中执行了 event.preventDefault(),并根据传进的 props 是 to 还是 replace 进行不同的操作。
NavLink 是基于 Link 的一个特殊版本,会在匹配当前 url 的元素添加一些传递的属性和样式:activeClassName 和 activeStyle ,源码部分是 Link 和 Route 的组合版本,这里就不深入了。
Redirect 在渲染时将会跳转到一个新的 url,这个新的 url 将会覆盖掉历史信息里面本该访问的那个地址。源码如下:
class Redirect extends React.Component { static propTypes = { computedMatch: PropTypes.object, // private, from <Switch> push: PropTypes.bool, from: PropTypes.string, to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired }; isStatic() { return this.context.router && this.context.router.staticContext; } componentWillMount() { if (this.isStatic()) this.perform(); } componentDidMount() { if (!this.isStatic()) this.perform(); } componentDidMount() { if (!this.isStatic()) this.perform(); } componentDidUpdate(prevProps) { //省略校验 this.perform(); } computeTo({ computedMatch, to }) { if (computedMatch) { if (typeof to === "string") { return generatePath(to, computedMatch.params); } else { return { ...to, pathname: generatePath(to.pathname, computedMatch.params) }; } } return to; } perform() { const { history } = this.context.router; const { push } = this.props; const to = this.computeTo(this.props); if (push) { history.push(to); } else { history.replace(to); } } render() { return null; } }
如上源码,Redirect 在组件的几个生命周期中都去执行了 perform 函数,perform 的功能就是通过判断是跳转路由还是覆盖路由从而进行 history 的相应操作,computeTo 功能主要是返回一个 url 进行页面跳转或者替换。和 matchPath 方法类似, computeTo 里调用的 generatePath 也有缓存的操作,对新产生的路由进行缓存,下次再进行 redirect 的时候直接返回,不需要再次通过 pathToRegexp 构造。
Switch 用来嵌套在 Route 外面,找出第一个匹配的 Route 进行渲染,其他的 Route 就不会去渲染。源码如下:
class Switch extends React.Component { static propTypes = { children: PropTypes.node, location: PropTypes.object }; render() { const { route } = this.context.router; const { children } = this.props; const location = this.props.location || route.location; let match, child; React.Children.forEach(children, element => { if (match == null && React.isValidElement(element)) { const { path: pathProp, exact, strict, sensitive, from } = element.props; const path = pathProp || from; child = element; match = matchPath( location.pathname, { path, exact, strict, sensitive }, route.match ); } }); return match ? React.cloneElement(child, { location, computedMatch: match }) : null; } }
首先会对 Children 进行遍历,然后使用上文提到的 matchPath 方法进行匹配,然后在 render 时只渲染匹配的那个 child。
整个 react-router 的源码还是比较简单的,没有什么特别晦涩的地方,基本上都能看懂,了解其内部源码的原理,在项目中就可以比较得心应手地使用了~
The text was updated successfully, but these errors were encountered:
No branches or pull requests
此文基于 react-router 4 进行讲解,如果对 react-router 还不太熟悉的童鞋可以移步 初探 React Router 4.0 和 react-router 的 API 文档地址 先学习一下。
react-router-dom 和 react-router
react-router-dom 中包含了 web 端所有路由相关的东西,它基于 react-router 进行了一些包装,所以项目中只需要引入 react-router-dom 即可。react-router 4 中强调万物即组件,其暴露的几乎所有东西都是一个 react 组件,正是因为这种 Just Components 的思想使得开发者更容易在项目中使用路由。
一张图显示出他们之间的关系:
从上图可知,react-router-dom 中的很多元素都是直接从 react-router 中拿过来的,只是在它的基础上进行了一些功能上的扩展。
源码分析
Router
Router 是创建路由最外面的包裹层组件,有点类似 Provider 的感觉,它的作用是监听路由的变化,从而渲染的页面组件。其源码如下:
源码部分中包含声明接受的 props 数据为 history 对象和要显示的 child 节点内容。并且在组件渲染之前(componentWillMount) 的时候,监听路由( history 是一个记录浏览器记录的一个库)的改变,当 url 发生更改时可以执行传递过去的 setState 回调,当 state 进行更新时,就会重新执行 render 对应的逻辑,从而引发页面的重新渲染。在 Router 组件销毁期间,取消对浏览器的监听。
history 有三种形式,分别是:
关于 history 的一些基础知识,可以移步 React Router预备知识,关于history的那些事 进行学习,这里不赘述。有三种 history, 对应的就有三种 Router:
这几种 Router 的源码只是改变了 history 的类型,然后传递给 Router 进行展示,以 BrowserRouter 为例,代码如下:
内容很简单,结合 Router 源码很好理解。HashRouter 和 MemoryRouter 的套路一样,在此不再赘述。
Route
Route 是嵌在 Router 里面的元素,如下:
它的作用是根据 url 进行匹配,如果匹配到当前路由,就展示对应的组件或者内容,否则显示 null。主要源码如下:
Route 在 componentWillReceiveProps 这个生命周期通过接受父级组件传来的 router 数据,从而来不断更新自己的 state 数据,进而达到重新渲染组件的效果。
其中 matchPath 这个方法,主要是用 path-to-regexp 这个库来匹配路由参数,并且将已经访问过的路由进行缓存,下次再匹配到这个路由的时候直接返回匹配的数据,其中缓存限制为 10000,具体的逻辑可以看 matchPath 源码,这里不贴出。
其中, strict, exact, sensitive 只是限定了一些匹配的规则,然后在 render 中进行渲染对应的渲染函数,根据传入的渲染规则不同,执行不同的渲染方式。
Link
Link 在浏览器中渲染出来就是一个 a 标签,只是动态赋给 a 一个 href 进行页面跳转,源码如下:
如上所示:渲染一个 a 标签,并获取一个 href,当点击的时候,触发点击的回调函数,为了防止页面刷新,需要禁掉浏览器的默认行为,所以在 handleClick 中执行了 event.preventDefault(),并根据传进的 props 是 to 还是 replace 进行不同的操作。
NavLink
NavLink 是基于 Link 的一个特殊版本,会在匹配当前 url 的元素添加一些传递的属性和样式:activeClassName 和 activeStyle ,源码部分是 Link 和 Route 的组合版本,这里就不深入了。
Redirect
Redirect 在渲染时将会跳转到一个新的 url,这个新的 url 将会覆盖掉历史信息里面本该访问的那个地址。源码如下:
如上源码,Redirect 在组件的几个生命周期中都去执行了 perform 函数,perform 的功能就是通过判断是跳转路由还是覆盖路由从而进行 history 的相应操作,computeTo 功能主要是返回一个 url 进行页面跳转或者替换。和 matchPath 方法类似, computeTo 里调用的 generatePath 也有缓存的操作,对新产生的路由进行缓存,下次再进行 redirect 的时候直接返回,不需要再次通过 pathToRegexp 构造。
Switch
Switch 用来嵌套在 Route 外面,找出第一个匹配的 Route 进行渲染,其他的 Route 就不会去渲染。源码如下:
首先会对 Children 进行遍历,然后使用上文提到的 matchPath 方法进行匹配,然后在 render 时只渲染匹配的那个 child。
总结
整个 react-router 的源码还是比较简单的,没有什么特别晦涩的地方,基本上都能看懂,了解其内部源码的原理,在项目中就可以比较得心应手地使用了~
The text was updated successfully, but these errors were encountered: