四川城乡和住房建设厅网站,找网页模板的网站,秀洲住房与建设局网站,内容营销包括用 react-router 也用了比较久了#xff0c;对他的内部工作方式却只是了解皮毛#xff0c;而且大部分还是通过别人的博客。最近两周打算自己探究一下他的实现。 注意#xff01;因为我只使用过 v3 版本的 react-router#xff0c;因为对他的使用方式比较熟悉#xff0c;所…用 react-router 也用了比较久了对他的内部工作方式却只是了解皮毛而且大部分还是通过别人的博客。最近两周打算自己探究一下他的实现。 注意因为我只使用过 v3 版本的 react-router因为对他的使用方式比较熟悉所以这次解析也是基于这个版本。
文章目录
react-router 工作模式简化流程内部具体实现Link 组件的实现方式 以及 *History.push 的实现方式为什么要这样实现有什么别的方式做个比较
react-router 工作模式简化流程
聊到这个话题就离不开前端路由。关于前端路由的一些演变过程和现有的方式可以看这篇文章。前端路由的重点就是不刷新页面现有的解决方案有 hashChange 和 popState 两种。 React 提供API也是围绕这两种方式。 共同点都是发布订阅的模式让浏览器事件触发的时候自己添加的 listener 被调用。Router 组件包裹着 Route 组件Route 组件负责描述每条路由的展示组件和匹配的路径。这样 Router 组件实际上会格式化出一个映射的路由表。 然而这是在页面路由更新的时候最开始进入页面的时候怎么办呢其实刚进入页面的时候也会进行一次匹配详细分析见下一部分。
内部具体实现
首先解答上面的遗留问题刚进入页面的状态如何带入这个问题我们可以和 Router 组件是在什么时候添加的事件监听放在一起解答。 componentWillMount() {//来源modules/Router.jsthis.transitionManager this.createTransitionManager()this.router this.createRouterObject(this.state)this._unlisten this.transitionManager.listen((error, state) {if (error) {this.handleError(error)} else {// Keep the identity of this.router because of a caveat in ContextUtils:// they only work if the object identity is preserved.assignRouterState(this.router, state)this.setState(state, this.props.onUpdate)}})},Router 组件在 willMount 生命周期添加了 listener而添加 listener 本身就会触发一次匹配路由展示的过程。匹配的过程有 match 方法用于各种嵌套路由的匹配。 但是注意如果使用的是 browserHistory这种路由方式一般是/a/b 这种方式可能需要后端同学的配合。
封装 history ——transitionManager
在上面的代码中我们会注意到添加监听器的 listen方法来自于 transitionManager 这个生成之后被赋值到 this.router 实例的属性。实际上 react-router 的事件监听过程是用 transitionManager 套了 history 这个库抹平各种前端路由方式的调用差异。history库本身暴露了一些API 比如监听、取消监听、跳转等一系列方法。有基于咱们刚才提到的 hash 和 state 两种方式。我们传给 Router 组件 history 属性的值其实就是他的实例。拿hashHistory 举栗下面的文件是 reate-router export 的 hashHistory 的来源也就是我们用的 hashHistory 的来源。
//来源modules/hashHistory.js
import createHashHistory from history/lib/createHashHistory
import createRouterHistory from ./createRouterHistory
export default createRouterHistory(createHashHistory)而 transitionManager 做的事情是针对当前的 router 实例和开发者指定的 history 对象对 history 库给的 API 做一次二次封装加上修改路由状态等等操作。然后开发者拿着 transitionManager 封装之后暴露出的 listen 等方法操作路由。 createTransitionManager() {//来源modules/Router.jsconst { matchContext } this.propsif (matchContext) {return matchContext.transitionManager}const { history } this.propsconst { routes, children } this.propsinvariant(history.getCurrentLocation,You have provided a history object created with history v4.x or v2.x and earlier. This version of React Router is only compatible with v3 history objects. Please change to history v3.x.)return createTransitionManager(//注意这个createTransitionManager才是history,createRoutes(routes || children))},渲染部分
渲染过程不是放在 Route 组件中负责渲染而是把状态都放在 Router 中保存详细可见第一部分的代码添加 listener 的部分。 assignRouterState(this.router, state)this.setState(state, this.props.onUpdate)而 Router 组件的 render 是这样写的 const { location, routes, params, components } this.stateconst { createElement, render, ...props } this.propsreturn render({...props,router: this.router,location,routes,params,components,createElement})而 props 的值是当前 Router 组件的状态他现在要展示的组件对应的地址当前跳转携带的参数 params 等等。下面是调用 render 的部分。 return RouterContext {...props} /RouterContext 包装组件的主要作用就是把 props参数中存有当前路由状态的对象router存到全局。类似于 Redux 的 Provider 组件。
Link 组件的实现方式
这里小伙伴们可以猜测一下Link是怎么做的呢 我们知道 Link最后渲染完是个 a 标签我们通常会给 Link 组件几个参数最常用的是跳转的路由地址和携带的参数。通过上面的讲解不难猜出Link 在点击的时候应该是调用了一个跳转的操作八成也是 history 库里给的然后禁止掉默认跳转就行了。 事实也是如此history 暴露了一个 push方法来 push 进浏览器的历史访问栈中。 这里再提一句另外一种用法*history.push() 的方式。这种其实就相当于直接点击了 a 标签一样的道理只不过用 js 的方式实现了。
为什么要这样实现有什么别的方式做个比较
我们可不可以尝试把展示交给 route 组件管理router 只控制激活当前的 route但是这样就不能支持通过 props 传给 router 路由配置的方式使用了这是其一 其二这样其实 route 其实负责了组件的渲染工作而不是把所有的状态和路由信息全部放在 router 中管理了不方便集中维护和扩展。 不知道小伙伴们还有别的看法吗
本来想写个浅析的……噼里啪啦写了一大堆……还捎带点语无伦次…… 但是虽然说了一堆……不过确实挺浅的……各位有兴趣可以尝试自己扒一下源码。建议 react-router 和 history 库一起 debug更有助于我们融会贯通 各位见笑了