我一直认为,会用框架和用好框架是有很大的区别的,当用框架到一定程度的时候,就需要看看框架对应生态中那些不可获取的库,这样能加深在不同框架中同样的功能的优秀实现方案。
了解React Router的实现原理
如何监听路有变化以及渲染对应的组件
React Router是什么?
React Router是React团队开发的基于React框架架构所实现的路由库。
React Router有多个版本。
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
这个库的内容并不在本文章的阅读范围内,有兴趣的可以自行查看。
源码解读
我们开始逐步开始阅读源码。我们使用React Router
的时候第一个了解的就是BrowserRouter
和HashRouter
这两个内置的组件。通过源码发现其实两个组件的实现是完全一样的,只是内部调用创建history
实例的方式不一样,一个调用createHashHistory
,另一个调用createBrowserHistory
。
官方demo
1 | export default class App extends React.Component { |
BrowserRouter
1 | // https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/BrowserRouter.js |
HashRouter
1 | // https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/HashRouter.js |
本篇文章是基于HashRouter
进行阅读,实际上只是监听的事件不一样而已。
通过源码发现,HashRouter
实例化了一个history
的实例,并且将history
实例通过props
和children
一起传入的Router
组件当中。
Router 组件
1 | // https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Router.js |
computeRootMatch函数中,如果pathname !== “/“的下,isExact会为false,后续会用到
Route 组件
1 | // https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js |
接下来我们看看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
当我们的路由发生变化时,Router
中所监听的history
函数将会触发,返回新的location
对象,这是将会触发Router
的setState
,然后对应所有绑定Router
的Route
都将会重新渲染判断是否命中路由来进行重新渲染。
Switch 组件
如果我们只是单纯的使用Route
组件来设置路由,当我们的当前的url
满足多条路由规则的情况下,会出现多个Route
的子组件进行渲染,这个时候如果当我们使用Switch
包裹多个Route
组件的话,那么只会渲染首先命中当前url
的Route
组件,具体是如何实现的呢?
1 | import React from "react"; |
所以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
等值的传递。