VueRouter和ReactRouter原理:$router与$route区别,见习React Router 4.0
Author:[email protected] Date:
前端路由概况
一般来说,无论是什么框架,要想实现路由方案,无外乎三种:Hash、History、Memory。前两者是浏览器原生支持,并有对应的事件通知外部其变化情况,第三个有点hack,纯粹是依靠js来模拟整个路由的切换,这就带来了用户无法与地址栏进行交互.
HashRouter哈希路由
它使用URL的哈希部分(即window.location.hash)来保持页面的UI与URL同步。
主要原理就是通过监听#后面URL变化后发出的浏览器hashchange事件,然后根据当前的路由标识,执行实现定义好的回调函数,刷新页面。
由于哈希路由采用#作为关键分割部分,劫持了其变化,所以原先页面通过锚点进行页面滚动定位的会产生冲突,导致定位到错误的路由,因此需要采用其他方案,来解决。
由于该技术只是用来支持旧版浏览器,因此更推荐大家使用 BrowserRouter。这里无需过多介绍。
BrowserRouter
使用HTML5 history API( pushState,replaceState和popstate事件),让页面的UI同步与URL。MDN文档:https://developer.mozilla.org/en-US/docs/Web/API/History
pushState,replaceState无刷新改变URL,popstate监听相关变化。
window.history.pushState(state, title, URL),如:window.history.pushState(null, null, "name=orange")
注意,URL不支持跨域
vue router 源码分析
源码分析
熟悉其基本用法后,开始看其源码就会好理解很多。整个结构分为如下几层:
components link.js:组件router-link view.js:组件router-view history base.js:下面三种路由模式的基类,里面定义了公有接口以及公有方法 abstract.js:abstract router模式,继承自base hash.js:hash router模式,继承自base html5.js:history router模式,继承自base util ...:工具类方法集,这里不再赘述 create-matcher.js:路由匹配表 create-route-map.js:路由匹配表 index.js:插件的总入口 install.js:提供安装方法
vue router的使用
分析源码前,先来看其使用方式:
import Vue from 'vue' import VueRouter from 'vue-router' import App from './App' //注册插件: 内部在浏览器环境下自动注册了插件,可以忽略 Vue.use(VueRouter) const routes = [ { path: '/red', component: '../demo/red' }, { path: '/yellow', component: ../demo/yellow' } ] const router = new VueRouter({ routes }) window.app = new Vue({ el: '#app', router, render: h => h(App) });
代码触发
由于所有的路由采用router-link包裹,而该组件内
原生事件被阻止了(preventDefault)
,转由执行内部的自定义方法,详细原理可查看上面路由的模拟实现内部自定义方法一般是
history.push
或history.replace
,这回触发history.transitionTo
而history.transitionTo则负责路由变化,其中包括变化
_route
,前面提到了Vue会监听该属性的话,然后通知对应的组件,执行其render方法!!!地址栏触发
地址栏变化了,一般会触发
popstate
或hashchange
事件,我们只需捕获到对应的变化,后面就是和上面的代码触发的流程一样了
reactRouter使用
render(( <Routerhistory={hashHistory}> <Route path="/"component={App}> {/* make them children of `App`*/} <Route path="/foo"component={Foo}/> <Route path="/bar"component={Bar}/> </Route> </Router> ), document.getElementById('app'))
vue-router 与 react-router 设计理念上的区别
总的来说,二者的设计理念大致相同,但是由于对应的框架分别是VUE和React,使得它们的使用方式略有些细微的差别。
react-router-cn 参考文档:http://react-guide.github.io/react-router-cn/docs/Introduction.html
vue-router参考文档:https://router.vuejs.org/
代码结构区别
react-router的典型代码实际上采用了子路由的方式,而vue-router仅用了并列级别的路由。
首先定义组件。定义Foo组件和Bar组件的方式的区别是VUE和React框架语法级别的区别,不在我们的讨论范围之内。
组件定义好之后,配置url和组件的对应关系。在典型代码中,vue-router定义了一个routes对象,它是一个数组,数组中每个对象表示该对应关系。而react-router定义采用了JSX方式,清晰地表示了这个对应关系,以及和/路由的父子关系。需要注意的是:VUE的路由配置要提供给new VueRouter()对象,这个对象要在全局VUE对象初始化时提供;而React路由则需要配置给全局<Router/>组件,虽然react-router也提供类似于vue-router典型代码中的对象数组形式的配置方式,但是最终仍是要将配置传递给<Router/>。**一个是全局配置(VUE),一个是全局组件(React),这是两者使用上的根本区别之一。**(vue-router并不提供像JSX这种类html的配置方式,它只能以对象方式提供路由配置,这也是框架系统不同所决定的)
子路由配置。vue-router在典型代码中并没有体现如何配置子路由,其实就vue-router路由组件的使用来说,无论是哪个级别的路由组件,**都会被渲染到父组件<router-view/>组件标识的地方**。对于react-router,**根路由会被渲染到<Router/>指定的位置,而子路由则会作为子组件,将children对象以参数方式传入父组件,由父组件将该对象指定渲染位置**。这也是为什么在典型代码中vue-router没有写路由的父子关系而react-router的典型代码体现了父子路由关系的原因。
使用时的不同点总结:
vue-router是全局配置方式,react-router是全局组件方式。
vue-router仅支持对象形式的配置,react-router支持对象形式和JSX语法的组件形式配置。
vue-router任何路由组件都会被渲染到<router-view/>位置,react-router子组件作为children被传入父组件,而根组件被渲染到<Router/>位置。
history区别
vue-router,mode: 'history'
react-router,直接使用 react-router 的话,用 BrowserRouter 将<App>包裹起来,或引入history(推荐,history可用于页面组件之外导航,此时就可以不用react-router-redux)
React Router 4.0
React Router 从 3.0 到 4.0 的改动,想来想去,认为是对于 URL 这个资源理解的变化。
URL 即浏览器地址,在前端数据化统一的浪潮下,其实 URL 也可以被看作是一种参数,在 React 中即一个 props 属性。
单页应用,如果从传统多页应用角度来思考,可能认为不过是一种体验的优化,或者是一种 “伪单页”,毕竟本质上单页应用只是一个页面而已。但换个角度想想,网站何尝不是一个整体,而网址的变化只是一种状态呢?
当我们做一个 Tabs 组件时,会发觉做得越来越像浏览器原生 tab,当用户给你提需求,在刷新浏览器时,能自动打开上一次打开的 Tab,我们的做法就是将当前打开的 Tab 信息保存在 URL 中,刷新时读取再切换过去。这证明了 URL 表示的就是一种状态。
而页面路由的状态化,是将模拟 Tab 的思路应用到了浏览器级别的 Tab。URL 是一种状态,在前端,可以通过浏览器地址自动获取,在后端,可以通过 req.url 获取,甚至可以手动传入来覆盖。
传统的开发思路:我们为每个 URL 编写独立的页面或者模块。将path映射为渲染模块,而且这种映射关系是静态的。只要程序一启动,映射关系就不能改变了。
新的开发思路:URL 是一个状态,代码读取这个状态作出不同展现,展现得完全不同时,可以看作传统模式的页面切换;但还可以做到只有某一块区域展现得不同。
react router4.0最值得乐道的改进
代码分割
通过 react-loadable,可以做到路由级别动态加载,或者更细粒度的模块级别动态加载:
const AsyncHome = Loadable({ loader: () => import('../components/Home/Home'), loading: LoadingPage })
当然上面展示的是 ReactRouter 中的用法,AsyncHome 可以在任何 JSX 中引用,这样就提升到了模块级别的动态加载。
无论是 webpack 的 Tree Shaking,还是动态加载,都只能以 Commonjs 的源码为分析目标,对 node_modules 中代码不起作用,所以 npm 包请先做好拆包。或者类似 antd 按照约定书写组件,并提供一种 webpack-loader 自动完成按需加载。
转场动画
通过 React Router Transition (Ant Motion 也很好用) 可以实现路由级别的动画:
<Router> <AnimatedSwitch atEnter={{ opacity: 0 }} atLeave={{ opacity: 0 }} atActive={{ opacity: 1 }} className="switch-wrapper" > <Route exact path="/" component={Home} /> <Route path="/about/" component={About}/> <Route path="/etc/" component={Etc}/> </AnimatedSwitch> </Router>
并提供了一些生命周期的回调,具体可以参考文档。现在动画的思路比较靠谱的也大致是这种:通过添加/移除 class 的方式,利用 css3 做动效。
withRouter是专门用来处理数据更新问题的
在使用一些redux的的connect()或者mobx的inject()的组件中,如果依赖于路由的更新要重新渲染,会出现路由更新了但是组件没有重新渲染的情况。这是因为redux和mobx的这些连接方法会修改组件的shouldComponentUpdate。
在使用withRouter解决更新问题的时候,一定要保证withRouter在最外层,比如withRouter(connect(Component))
滚动条复位
当页面回退时,将滚动条恢复到页面最顶部,可以让单页路由看起来更加正常。由于 React Router4.0 中,路由是一种组件,我们可以利用 componentDidUpdate 简单完成滚动条复位的功能:
<Router history={history}> <ScrollToTop> <div> <Switch> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> <Route path="*" component={NotFound} /> </Switch> </div> </ScrollToTop> </Router> @withRouter class ScrollToTop extends Component { componentDidUpdate(prevProps) { if (this.props.location !== prevProps.location) { window.scrollTo(0, 0) } } render() { return this.props.children } }
非通过 Route 渲染的组件,可以通过 withRouter 拿到路由信息,仅当其为 Router 的子元素时有效。
嵌套路由
React Router4.0 嵌套路由与 3.0 不同,是通过组件 Route 的嵌套实现的。
在任何组件,都可以使用如下代码实现嵌套路由:
<Route path={`${this.props.match.url}/:id`} component={NestComponent} />
这样将路由功能切分到各个组件中,我现在的项目甚至已经没有 route.js 文件了,路由由 layout 与各个组件自身承担。这种设计思路与 Nestjs 的描述性路由具有相同的思想 - 在 nodejs 中,我们可以通过装饰器,在任意一个 Action 上描述其访问的 URL:
@POST("/api/service") async someAction() {}
实现路由页面页面刷新数据不丢失的方案
HashRouter有两种方式(url传值,路由参数传值)
BorwserRouter有三种方式(url传值,路由参数传值,以及state)
本地缓存或者状态管理方案
参考文章:
前端路由实现与 react-router 源码分析 https://www.cnblogs.com/passkey/p/10170422.html
从VueRouter和ReactRouter源码深入理解前端路由原理 https://andyzou.cn/2019/02/26/cjsmz89yz001tlbysp5wxorb8/
http://zhangdajia.com/2018/11/30/React-router-v4中BrowserRouter和HashRouter的区别/
初探 React Router 4.0 https://www.jianshu.com/p/e3adc9b5f75c/
精读《React Router4.0 进阶概念》https://zhuanlan.zhihu.com/p/31178105
React Router 4.0 实现路由守卫 https://www.jianshu.com/p/677433245697
React Router v4 几乎误我一生 https://zhuanlan.zhihu.com/p/27433116
Vue-Router和React-Routerd对比 https://blog.csdn.net/hyupeng1006/article/details/80756304
转载本站文章《VueRouter和ReactRouter原理:$router与$route区别,见习React Router 4.0》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue/4832.html