2020年的春节是一个多灾多难的春节,新型冠状病毒的出现折磨着每一个中国人的心,导致不少公司都安排节后在家办公,但是在这个时候,作为一名小前端也是要继续努力学习,所哟2020年的第一篇文章就从React Router的源码阅读开始,继续了解React的体系。
前言
为什么去看React Router的源码?
- 了解React Router的实现原理
- 如何监听路有变化以及渲染对应的组件
我一直认为,会用框架和用好框架是有很大的区别的,当用框架到一定程度的时候,就需要看看框架对应生态中那些不可获取的库,这样能加深在不同框架中同样的功能的优秀实现方案。
React Router是什么?
React Router是React团队开发的基于React框架架构所实现的路由库。
Github
React Router有多个版本。
一般前端写web页面多数是使用react-router-dom这个库,那么react-router和react-router-dom有什么区别呢?
其实react-router-dom是基于react-router再封装的一个带有React DOM组件的库,其中包括了Link、HashRouter、BrowserRouter等组件提供给开发者通过使用标签的方式控制路由跳转。
阅读须知
- 源码阅读基于react-router和react-router-dom 5.2.1版本
React Router如何监听路由变化的?
通过查看源码发现,react-router使用了一个history的库来监听不同的路由变化,react-router支持我们使用hash和bowser两种路由规则,所以history这个库可以根据调用的api不同,来区分当前是监听不同的路由方式。
history
history这个库的内容并不在本文章的阅读范围内,有兴趣的可以自行查看。
我们开始逐步开始阅读源码。我们使用React Router的时候第一个了解的就是BrowserRouter和HashRouter这两个内置的组件。通过源码发现其实两个组件的实现是完全一样的,只是内部调用创建history实例的方式不一样,一个调用createHashHistory,另一个调用createBrowserHistory。
基于一个官方demo展开阅读:
代码语言:javascript复制export default class App extends React.Component {
render () {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
</ul>
<hr />
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/dashboard">
<Dashboard />
</Route>
</div>
</Router>
);
}
}
BrowserRouter.jsx
HashRouter.jsx
本篇文章是基于HashRouter进行阅读,实际上只是监听的事件不一样而已。
通过源码发现,HashRouter实例化了一个history的实例,并且将history实例通过props和children一起传入的Router组件当中。
接下来是Router组件。
computeRootMatch函数中,如果pathname !== "/"的下,isExact会为false,后续会用到
Route组件
接下来我们看看matchPath函数是如何判断当前的url是否命中当前Route组件的path的。
到这里,就是大概整体渲染的时候React Router做了什么事情。
总结:
- HashRouter
- 实例化history,调用createHashHistory
- 将children和history传入Router组件
- Router
- constructor周期内监听history的路由事件,将新的location存到Router的state中
- componentWillUnmount移除监听
- 使用Context包裹子组件(Provider),存入history、location、match(默认的命中对象)等。
- Route
- 使用Context,声明为Consumer,接收Router传入的值。
- 调用matchPath函数来判断当前Route的path是否命中当前url。
- 使用Context包裹子组件(Provider),将Router传递进来的参数以及命中结果等传入给Route包裹的子组件
- 渲染循序如下:
- 当前Route是否命中url
- 是
- 判断当前Route是否有子组件,有那么将渲染子组件,否则进入下一条
- 判断当前Route是否有component参数,有就执行React.createElement创建component,否则进入下一条
- 判断当前Route是否有render参数(函数),有就执行render函数,否则进入下一条。
- 返回null
- 否
- 返回null
- 是
- 当前Route是否命中url
当我们的路由发生变化时,Router中所监听的history函数将会触发,返回新的location对象,这是将会触发Router的setState,然后对应所有绑定Router的Route都将会重新渲染判断是否命中路由来进行重新渲染。
Switch组件
如果我们只是单纯的使用Route组件来设置路由,当我们的当前的url满足多条路由规则的情况下,会出现多个Route的子组件进行渲染,这个时候如果当我们使用Switch包裹多个Route组件的话,那么只会渲染首先命中当前url的Route组件,具体是如何实现的呢?
所以Switch和Route的区别是在于,Switch只会渲染满足的条件的Route,而Route会根据传入的path来判断如果满足当前url的情况下,就会渲染Route的子组件。Switch就是从而实现Route同时只会命中一个的功能。
Link组件
Link组件也是相当简单的一个组件,内部主要做了以下事情:
- 判断传入参数replace,是使用replace还是push进行跳转
- 执行传入的onClick事件
- 判断一些参数,例如(传入_blank参数,将交由浏览器处理)
- 触发内部点击事件,使用history库实例后的push或replace来控制前端路由跳转
- 禁止默认事件
以下是Link组件的点击处理逻辑:
Link组件是如何获取到history的那,我们使用的时候并没有传递进去当前的history实例呀,实际上还记得之前看Route组件的时候,在return的时候,又包裹了一层Context吗,其实实际上就是给Link这类型的标签方便获取到history实例的,而Link组件也是使用Context。
结语
React Router的代码其实很好理解,主要涉及到的是history这个库是核心点,整个路由的触发事件的封装,抹平了浏览器差异。其次就是React Router实际是基于context来实现Router、Route、Link等组件中,history,location等值的传递。