• home > webfront > ECMAS > vue >

    VueRouter和ReactRouter原理:$router与$route区别,见习React Router 4.0

    Author:zhoulujun@live.cn Date:

    实现路由方案无外乎三种:Hash、History、Memory。前两者是浏览器原生支持,并有对应的事件通知 。hash原来是用作锚点定位。现在一般用HTML5 history API处理前端路由,pushState,replaceState无刷新改变页面URL地址,popstate监听相关变化。

    前端路由概况

    一般来说,无论是什么框架,要想实现路由方案,无外乎三种:Hash、History、Memory。前两者是浏览器原生支持,并有对应的事件通知外部其变化情况,第三个有点hack,纯粹是依靠js来模拟整个路由的切换,这就带来了用户无法与地址栏进行交互.

    HashRouter哈希路由

    它使用URL的哈希部分(即window.location.hash)来保持页面的UI与URL同步

    主要原理就是通过监听#后面URL变化后发出的浏览器hashchange事件,然后根据当前的路由标识,执行实现定义好的回调函数,刷新页面。

    由于哈希路由采用#作为关键分割部分,劫持了其变化,所以原先页面通过锚点进行页面滚动定位的会产生冲突,导致定位到错误的路由,因此需要采用其他方案,来解决。

    由于该技术只是用来支持旧版浏览器,因此更推荐大家使用 BrowserRouter。这里无需过多介绍。

    BrowserRouter

    使用HTML5 history API( pushState,replaceState和popstate事件),让页面的UI同步与URLMDN文档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.pushhistory.replace,这回触发history.transitionTo

      • 而history.transitionTo则负责路由变化,其中包括变化_route,前面提到了Vue会监听该属性的话,然后通知对应的组件,执行其render方法!!!

    • 地址栏触发

      • 地址栏变化了,一般会触发popstatehashchange事件,我们只需捕获到对应的变化,后面就是和上面的代码触发的流程一样了


    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的典型代码实际上采用了子路由的方式,而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