Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

谈谈前端路由 #12

Open
chiwent opened this issue May 26, 2019 · 0 comments
Open

谈谈前端路由 #12

chiwent opened this issue May 26, 2019 · 0 comments

Comments

@chiwent
Copy link
Owner

chiwent commented May 26, 2019

谈谈前端路由

什么是路由

对于用户来说,路由就是浏览器地址栏中的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值变动的方式有两种:

  • 通过设置a标签的href属性,点击a标签后会切换URL,触发hashchange
  • 直接将hash值赋值给location.hash,比如location.hash = '#index
<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)

  • 状态对象(state object):js对象,和使用pushState创建的新历史记录相关联,无论何时用户导航到新创建的状态,都会触发popState,并且可以在popState中使用该对象
  • 标题(title):传入标题给当前的状态对象,多数浏览器不支持或者会自动忽略该选型,所以一般传入null
  • 地址(url):新历史记录地址,可选,默认为当前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;



参考:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant