chiwent / blog

个人博客,只在issue内更新
https://chiwent.github.io/blog
8 stars 0 forks source link

谈谈前端路由 #12

Open chiwent opened 5 years ago

chiwent commented 5 years ago

谈谈前端路由

什么是路由

对于用户来说,路由就是浏览器地址栏中的url;而对于开发者来说,路由主要的工作是匹配url和对应的处理函数,访问的url会映射到对应的函数中。简单的说,路由就是UTL到函数的映射。

什么是前端路由,什么是后端路由

在早期的web开发中,后端MVC是主流的开发模式,比如PHP、JSP之类的,后端会将模板渲染成HTML返回到浏览器,用户是通过URL访问到对应的页面,后端匹配到路由后就返回对应的HTML。在切换不同的页面(路由)时,前端都会向后端重新发出一次请求。而如果遇到了无法匹配的路由,后端则会返回404状态码。
后端路由的问题:
在切换路由的时候,浏览器需要向后端重新发起一次请求,全局刷新加上网络延迟,会破坏用户体验(尽管可以用ajax一定程度上缓解)。

往后,web开发进入单页应用(SPA)时代,浏览器在向服务端发起请求后,服务端会向浏览器发送一个简单的HTML模板文件和一些js依赖文件,然后由浏览器解析js并将数据注入到HTML中。这样可以使页面的响应速度更快,体验更友好。在SPA应用中,页面跳转的规则交由前端控制,在切换路由时,不需要向后端发送请求,只要解析js即可。

前端路由的问题:
使用浏览器的前进后退按键,会向后端重新发起请求,没有合理使用缓存。
无法记住滚动的位置,不能在使用前进后退按键时,返回原来的页面位置(比如在页面使用锚点的时候)

前端路由的实现

前端路由可以分为hash路由和history路由,其中hash路由的URL中带有#,后面会形成一个hash,可以通过window.location.hash拿到对应值。当URL中的hash值发送改变,会触发hashchange注册的回调,然后执行不同的操作。每次hash值的变动,都会在浏览器的访问历史中增加一个记录,可以通浏览器的前进后退来控制hash切换(会重新发起请求)。hash路由的兼容性好;history路由利用H5的history对应的API来完成路由控制,不会带有#,但兼容性较差,并且需要后台的配合,否则在不匹配的情况下会返回404。

hash路由的简单实现

触发hash值变动的方式有两种:

<body>
    <ul>
        <li><a href="#/">Index</a></li>
        <li><a href="#/album">Album</a></li>
        <li><a href="#/cate">category</a></li>
    </ul>

    <script>
        function HashRouter() {
            // 以键值对性质存储路由
            this.routes = {};
            // 当前路由URL
            this.currentUrl = '';
        }
        // 存储更新的path路径和对应的回调,回调负责hash更新后的事务
        HashRouter.prototype.route = function(path, callback) {
            this.routes[path] = callback || function(){};
        };
        // 执行当前url对应的回调, 刷新页面
        HashRouter.prototype.refresh = function() {
            // 获取当前URL中的hash路径
            this.currentUrl = location.hash.slice(1) || '/';
            this.routes[this.currentUrl]();
        };
        // 监听浏览器url hash更新
        HashRouter.prototype.init = function() {
            window.addEventListener('load', this.refresh.bind(this), false);
            window.addEventListner('hashchange', this.refresh.bind(this), false);
        };
        window.HashRouter = new HashRouter();
        window.HashRouter.init();
        HashRouter.route('/', function() {
            // 切换路由的操作
        });
        HashRouter.route('/album', function() {
            // 切换路由的操作
        });
        HashRouter.route('/cate', function() {
            // 切换路由的操作
        });
    </script>
</body>

history路由的简单实现

history路由主要是通过H5的history对象实现的,设计的API主要是history.pushStatehistory.replaceStatehistory.popState,其中前两者可以修改URL地址,而最后的事件可以监听地址变化,并且手动pushState并不会触发popState。

pushState和replaceState都接收3个参数,格式:window.history.pushState(stateObj, title, url)

<body>
    <ul>
        <li><a href="/">Index</a></li>
        <li><a href="/album">Album</a></li>
        <li><a href="/cate">category</a></li>
    </ul>
    <script>
        function HisoryRouter() {
            this.routes = {};
        }
        HistoryRouter.prototype.init = function(path) {
            history.replaceState({
                path: path
            }, null, path);
            this.routes[path] && this.routes[path]();
        }
        HistoryRouter.prototype.route = function(path, callback) {
            this.routes[path] = callback || function(){};
        }
        HistoryRouter.prototype.go = function(path) {
            history.pushState({
                path: path
            }, null, path);
            this.routes[path] && this.routes[path]();
        }
        HistoryRouter.prototype.onPopState = function () {
            window.addEventListener('popstate', function(e) {
                var path = e.state && e.state.path;
                this.routes[path] && this.routes[path]();
            });
        }
        window.HistoryRouter = new HistoryRouter();
        window.HistoryRouter.init();
        HistoryRouter.route('/', function() {
            // 切换路由的操作
        });
        HistoryRouter.route('/album', function() {
            // 切换路由的操作
        });
        HistoryRouter.route('/cate', function() {
            // 切换路由的操作
        });
    </script>
</body>

目前有很多MVVM前端框架都配备了对应的前端路由工具,如Vue下的VueRouter。在使用VueRouter的时候,默认选择的是hash模式,如果要使用history模式,可以在对配置进行修改。另外,由于浏览器的路由跳转是交给前端路由控制了,所以后端将不处理404的错误界面,因为服务端会对所有路径都返回那一个HTML模板文件。所以,为了要在前端正确处理404,应该加上如下配置:

const router = new VueRouter({
    mode: 'history',
    routes: [{
        path: '*', component: NotFoundPage
    }]
});

或者像下面那样处理(较繁琐):

// https://blog.csdn.net/weixin_37861326/article/details/82383465
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// 404
import Errorinfo from '@/components/error404'

const router = new Router({
  routes: [
    // 404page
    {
      path: '/errorinfo',
      name: 'Errorinfo',
      component: Errorinfo
    }
  ],
  scrollBehavior(to, from, savedPosition) {
    return {
      x: 0,
      y: 0
    }
  },
  history: true
})
router.beforeEach((to, from, next) => {
  if (to.matched.length === 0) { 
    from.name ? next({
      name: from.name
    }) : next('/errorinfo'); 
  } else {
    next(); //如果匹配到正确跳转
  }
});
export default router;



参考: