Vue-router:13、Hash模式与History模式

13.1 Hash模式与History模式区别

前端路由中,不管是什么实现模式,都是客户端的一种实现方式,也就是当路径发生变化的时候,是不会向服务器发送请求的。

如果需要向服务器发送请求,需要用到ajax方式。

两种模式的区别

首先是表现形式的区别

Hash模式

https://www.baidu.com/#/showlist?id=22256

hash模式中路径带有#, #后面的内容作为路由地址。可以通过问号携带参数。

当然这种模式相对来说比较丑,路径中带有与数据无关的符号,例如#?

History模式

https://www.baidu.com/showlist/22256

History模式是一个正常的路径的模式,如果要想实现这种模式,还需要服务端的相应支持。

下面再来看一下两者原理上的区别。

Hash模式是基于锚点,以及onhashchange事件。

通过锚点的值作为路由地址,当地址发生变化后触发onhashchange事件。

History模式是基于HTML5中的History API

也就是如下两个方法

history.pushState( ) IE10以后才支持

history.replaceState( )

13.2 History模式的使用

History模式需要服务器的支持,为什么呢?

因为在单页面的应用中,只有一个页面,也就是index.html这个页面,服务端不存在http://www.test.com/login这样的地址,也就说如果刷新浏览器,

请求服务器,是找不到/login这个页面的,所以会出现404的错误。(在传统的开发模式下,输入以上的地址,会返回login这个页面,而在单页面应用中,只有一个页面为index.html

所以说,在服务端应该除了静态资源外都返回单页应用的index.html

下面我们开始history模式来演示一下对应的问题。

首先添加一个针对404组件的处理

首先在菜单栏中添加一个链接:

 <!-- 左侧菜单栏 -->
          <div class="content left">
            <ul>
              <li><router-link to="/users"> 用户管理</router-link></li>
              <li><router-link to="/rights"> 权限管理</router-link></li>
              <li><router-link to="/goods"> 商品管理</router-link></li>
              <li><router-link to="/orders"> 订单管理</router-link></li>
              <li><router-link to="/settings"> 系统设置</router-link></li>
              <li><router-link to="/about"> 关于</router-link></li>
            </ul>
          </div>

这里我们添加了一个“关于”的链接,但是我们没有为其定义相应的组件,所以这里需要处理404的情况。

  const NotFound = {
        template: `<div>
            你访问的页面不存在!!
          </div>`,
      };

在程序中添加了一个针对404的组件。

 const router = new VueRouter({
        mode: "history",
        const router = new VueRouter({
        mode: "history",
        routes: [
          { path: "/login", component: Login },
          { path: "*", component: NotFound },
          {
            path: "/",
            component: App,
            redirect: "/users",
            children: [
              {
                path: "/users",
                component: Users,
                meta: {
                  auth: true,
                },
                // beforeEnter(to, from, next) {
                //   if (window.isLogin) {
                //     next();
                //   } else {
                //     next("/login?redirect=" + to.fullPath);
                //   }
                // },
              },
              { path: "/userinfo/:id", component: UserInfo, props: true },
              { path: "/rights", component: Rights },
              { path: "/goods", component: Goods },
              { path: "/orders", component: Orders },
              { path: "/settings", component: Settings },
            ],
          },
        ],
      });

在上面的代码中,指定了处理404的路由规则,同时将路由的模式修改成了history模式。同时,启用这里启用了其它的组件的路由规则配置,也就是不在login方法中使用addRoutes方法来动态添加路由规则了。

login 方法修改成如下形式:

 login() {
            // window.isLogin = true;
            window.sessionStorage.setItem("isLogin", true);
            if (this.$route.query.redirect) {
              //   //动态添加路由:
              //   this.$router.addRoutes([
              //     {
              //       path: "/",
              //       component: App,
              //       redirect: "/users",
              //       children: [
              //         {
              //           path: "/users",
              //           component: Users,
              //           meta: {
              //             auth: true,
              //           },
              //           // beforeEnter(to, from, next) {
              //           //   if (window.isLogin) {
              //           //     next();
              //           //   } else {
              //           //     next("/login?redirect=" + to.fullPath);
              //           //   }
              //           // },
              //         },
              //         { path: "/userinfo/:id", component: UserInfo, props: true },
              //         { path: "/rights", component: Rights },
              //         { path: "/goods", component: Goods },
              //         { path: "/orders", component: Orders },
              //         { path: "/settings", component: Settings },
              //       ],
              //     },
              //   ]);
              this.$router.push(this.$route.query.redirect);
            } else {
              this.$router.push("/");
            }
          }

现在已经将前端vue中的代码修改完毕了,下面我们要将页面的内容部署到node.js服务器中。

而且上面的代码中,我们使用了sessionStorage来保存登录用户的信息,不在使用window下的isLogin

对应的data内容下的代码也要修改:

const Login = {
        data() {
          return {
            isLogin: window.sessionStorage.getItem("isLogin"),
          };
        },

路由守卫中的代码进行如下修改:

jsrouter.beforeEach((to, from, next) => {
        //to:去哪个页面,from来自哪个页面,next继续执行.
        if (window.sessionStorage.getItem("isLogin")) {
          //用户已经登录
          if (to.path === "/login") {
            // 用户已经登录了,但是又访问登录页面,这里直接跳转到用户列表页面
            next("/");
          } else {
            //用户已经登录,并且访问其它页面,则运行访问

            next();
          }
        } else {
          //用户没有登录,并且访问的就是登录页,则运行访问登录页
          if (to.path === "/login") {
            next();
          } else {
            //用户没有登录,访问其它页面,则跳转到登录页面。
            next("/login?redirect=" + to.fullPath);
          }
        }
      });

在上面的代码中,我们也是通过sessionStorage来获取登录信息。

index.html完整代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>基于vue-router的案例</title>
    <script src="./lib/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    
  </head>
  <body>
    <div id="app"><router-view></router-view></div>
    <script>
      const App = {
        template: `<div>
        <!-- 头部区域 -->
        <header class="header">传智后台管理系统</header>
        <!-- 中间主体区域 -->
        <div class="main">
          <!-- 左侧菜单栏 -->
          <div class="content left">
            <ul>
              <li><router-link to="/users"> 用户管理</router-link></li>
              <li><router-link to="/rights"> 权限管理</router-link></li>
              <li><router-link to="/goods"> 商品管理</router-link></li>
              <li><router-link to="/orders"> 订单管理</router-link></li>
              <li><router-link to="/settings"> 系统设置</router-link></li>
              <li><router-link to="/about"> 关于</router-link></li>
            </ul>
          </div>
          <!-- 右侧内容区域 -->
          <div class="content right"><div class="main-content">
            <keep-alive include='goods'>
             <router-view />
             </keep-alive>
             </div></div>
        </div>
        <!-- 尾部区域 -->
        <footer class="footer">版权信息</footer>
      </div>`,
      };
      const Users = {
        data() {
          return {
            userlist: [
              { id: 1, name: "张三", age: 10 },
              { id: 2, name: "李四", age: 20 },
              { id: 3, name: "王五", age: 30 },
              { id: 4, name: "赵六", age: 40 },
            ],
          };
        },
        methods: {
          goDetail(id) {
            console.log(id);
            this.$router.push("/userinfo/" + id);
          },
        },
        template: `<div>
          <h3>用户管理区域</h3>
          <table>
            <thead>
              <tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
            </thead>
            <tbody>
              <tr v-for="item in userlist" :key="item.id">
                <td>{{item.id}}</td>
                <td>{{item.name}}</td>
                <td>{{item.age}}</td>
                <td>
                  <a href="javascript:;" @click="goDetail(item.id)">详情</a>
                </td>
              </tr>
            </tbody>
          </table>
        </div>`,
        // beforeRouteEnter(to, from, next) {
        //   if (window.isLogin) {
        //     next();
        //   } else {
        //     next("/login?redirect=" + to.fullPath);
        //   }
        // },
      };
      //用户详情组件
      const UserInfo = {
        props: ["id"],
        template: `<div>
            <h5>用户详情页 --- 用户Id为:{{id}}</h5>
            <button @click="goback()">后退</button>
          </div>`,
        methods: {
          goback() {
            // 实现后退功能
            this.$router.go(-1);
          },
        },
      };

      const Rights = {
        template: `<div>
          <h3>权限管理区域</h3>
        </div>`,
      };
      const Goods = {
        name: "goods",
        template: `<div>
          <h3>商品管理区域</h3>
        </div>`,
        created() {
          console.log(new Date());
        },
      };
      const Orders = {
        template: `<div>
          <h3>订单管理区域</h3>
        </div>`,
      };
      const Settings = {
        template: `<div>
          <h3>系统设置区域</h3>
        </div>`,
      };
      const NotFound = {
        template: `<div>
            你访问的页面不存在!!
          </div>`,
      };
      const Login = {
        data() {
          return {
            isLogin: window.sessionStorage.getItem("isLogin"),
          };
        },

        template: `<div>
            <button @click="login" v-if="!isLogin">登录</button>
            <button @click="logout" v-else>注销</button>
            </div>`,
        methods: {
          login() {
            // window.isLogin = true;
            window.sessionStorage.setItem("isLogin", true);
            if (this.$route.query.redirect) {
              //   //动态添加路由:
              //   this.$router.addRoutes([
              //     {
              //       path: "/",
              //       component: App,
              //       redirect: "/users",
              //       children: [
              //         {
              //           path: "/users",
              //           component: Users,
              //           meta: {
              //             auth: true,
              //           },
              //           // beforeEnter(to, from, next) {
              //           //   if (window.isLogin) {
              //           //     next();
              //           //   } else {
              //           //     next("/login?redirect=" + to.fullPath);
              //           //   }
              //           // },
              //         },
              //         { path: "/userinfo/:id", component: UserInfo, props: true },
              //         { path: "/rights", component: Rights },
              //         { path: "/goods", component: Goods },
              //         { path: "/orders", component: Orders },
              //         { path: "/settings", component: Settings },
              //       ],
              //     },
              //   ]);
              this.$router.push(this.$route.query.redirect);
            } else {
              this.$router.push("/");
            }
          },
          logout() {
            this.isLogin = window.isLogin = false;
          },
        },
      };

      // 创建路由对象
      const router = new VueRouter({
        mode: "history",
        routes: [
          { path: "/login", component: Login },
          { path: "*", component: NotFound },
          {
            path: "/",
            component: App,
            redirect: "/users",
            children: [
              {
                path: "/users",
                component: Users,
                meta: {
                  auth: true,
                },
                // beforeEnter(to, from, next) {
                //   if (window.isLogin) {
                //     next();
                //   } else {
                //     next("/login?redirect=" + to.fullPath);
                //   }
                // },
              },
              { path: "/userinfo/:id", component: UserInfo, props: true },
              { path: "/rights", component: Rights },
              { path: "/goods", component: Goods },
              { path: "/orders", component: Orders },
              { path: "/settings", component: Settings },
            ],
          },
        ],
      });
      //实现全局守卫
      // router.beforeEach((to, from, next) => {
      //   //to:去哪个页面,from来自哪个页面,next继续执行.
      //   //判断哪个路由需要进行守卫,这里可以通过元数据方式
      //   if (to.meta.auth) {
      //     if (window.isLogin) {
      //       next();
      //     } else {
      //       next("/login?redirect=" + to.fullPath);
      //     }
      //   } else {
      //     next();
      //   }
      // });

      router.beforeEach((to, from, next) => {
        //to:去哪个页面,from来自哪个页面,next继续执行.
        if (window.sessionStorage.getItem("isLogin")) {
          //用户已经登录
          if (to.path === "/login") {
            // 用户已经登录了,但是又访问登录页面,这里直接跳转到用户列表页面
            next("/");
          } else {
            //用户已经登录,并且访问其它页面,则运行访问

            next();
          }
        } else {
          //用户没有登录,并且访问的就是登录页,则运行访问登录页
          if (to.path === "/login") {
            next();
          } else {
            //用户没有登录,访问其它页面,则跳转到登录页面。
            next("/login?redirect=" + to.fullPath);
          }
        }
      });

      const vm = new Vue({
        el: "#app",
        router,
      });
    </script>
  </body>
</html>

当然,项目的目录结构做了一定的调整,如下图所示:
在这里插入图片描述
web目录下面,存放的是index.html,在webserver目录下面存放的是node代码。

下面看一下具体的node代码的实现。

app.js文件中的代码如下

const path = require("path");
//导入处理history模式的模块
const history = require("connect-history-api-fallback");
const express = require("express");
const app = express();
//注册处理history模式的中间件
// app.use(history())
//处理静态资源的中间件,处理web目录下的index.html
app.use(express.static(path.join(__dirname, "../web")));
app.listen(3000, () => {
  console.log("服务器开启");
});

connect-history-api-fallback模块的安装如下(注意在上面的代码中还没有使用该模块)

npm install --save connect-history-api-fallback

下面还需要安装express

// An highlighted block
var foo = 'bar';

启动服务

node app.js

现在在地址栏中输入:http://localhost:3000就可以访问网站了。

并且当我们去单击左侧的菜单的时候,可以实现页面的切换,同时单击“关于”的时候,会出现NotFound组件中的内容。

经过测试发现好像没有什么问题,那这是什么原因呢?你想一下当我们单击左侧菜单的时候,路由是怎样工作的呢?

因为现在我们开启了路由的history模式,而该模式是通过HTML5中的history中的api来完成路由的操作的,也就是当我们单击菜单的时候,是通过history.pushState( )方法来修改地址栏中的地址,实现组件的切换,而且还会把地址保存的历史记录中(也就是可以单击浏览器中后退按钮,实现后退等操作),但是它并不会向服务器发送请求。

所以说现在整个操作都是在客户端完成的。

但是,当我刷新了浏览器以后,会出现怎样的情况呢?
在这里插入图片描述
上图的含义就是,当单击浏览器中的刷新按钮的时候,会向服务器发送请求,要求node服务器处理这个地址,但是服务器并没有处理该地址,所以服务器会返回404

以上就是如果vue-router开启了history模式后,出现的问题。

下面解决这个问题,在服务端启用connect-history-api-fallback模块就可以了,如下代码所示:

const path = require("path");
//导入处理history模式的模块
const history = require("connect-history-api-fallback");
const express = require("express");
const app = express();
//注册处理history模式的中间件
app.use(history());
//处理静态资源的中间件
app.use(express.static(path.join(__dirname, "../web")));
app.listen(3000, () => {
  console.log("服务器开启");
});

服务端的代码做了修改以后,一定要服务端重新启动node app.js

然后经过测试以后发现没有问题了。

那么现在你考虑一下,具体的工作方式是什么?

当我们在服务端开启对history模式的支持以后,我们刷新浏览器,会想服务器发送请求,例如:http://localhost:3000/orders

服务器接收该请求,那么用于服务器开启了history模式,然后服务器会检查,根据该请求所访问的页面是不存在的,所以会将单页面应用的index.html返回给浏览器。浏览器接收index.html页面后,会判断路由地址,发现地址为orders,所以会加载该地址对应的组件内容。

13.3 在Nginx服务器中配置History模式

代理服务器

代理服务器:一般是指局域网内部的机器通过代理服务器发送请求到互联网上的服务器,代理服务器一般作用在客户端。应用比如:GoAgent,翻墙神器.
在这里插入图片描述
反向代理服务器

**反向代理服务器:**在服务器端接受客户端的请求,然后把请求分发给具体的服务器进行处理,然后再将服务器的响应结果反馈给客户端。Nginx就是其中的一种反向代理服务器软件。
在这里插入图片描述
在这里插入图片描述
Nginx简介

Nginx (“engine x”) ,Nginx (“engine x”) 是俄罗斯人Igor Sysoev(塞索耶夫)编写的一款高性能的 HTTP 和反向代理服务器。也是一个IMAP/POP3/SMTP代理服务器;也就是说,Nginx本身就可以托管网站,进行HTTP服务处理,也可以作为反向代理服务器使用。

Nginx的应用现状

淘宝、新浪博客、新浪播客、网易新闻、六间房、56.com、Discuz!、水木社区、豆瓣、YUPOO、海内、迅雷在线 等多家网站使用 Nginx 作为Web服务器或反向代理服务器。

Nginx的特点

  • 跨平台:Nginx 可以在大多数 Unix like OS编译运行,而且也有Windows的移植版本。

  • 配置异常简单:非常容易上手。配置风格跟程序开发一样,神一般的配置

  • 非阻塞、高并发连接:数据复制时,磁盘I/O的第一阶段是非阻塞的。官方测试能够支撑5万并发连接,在实际生产环境中跑到2~3万并发连接数.

 高并发:其实就是使用技术手段使得系统可以并行处理很多的请求!衡量指标常用的有响应时间,吞吐量,每秒查询率QPS,并发用户数。响应时间:系统对请求做出响应的时间。你简单理解为一个http请求返回所用的时间。

吞吐量:单位时间内处理的请求数量。

QPS:每秒可以处理的请求数

并发用户数:同时承载正常使用系统功能的用户数量。也就是多少个人同时使用这个系统,这个系统还能正常运行。这个用户数量就是并发用户数了

  • 内存消耗小:处理大并发的请求内存消耗非常小。在3万并发连接下,开启的10个Nginx 进程才消耗150M内存(15M*10=150M)。

  • 成本低廉:Nginx为开源软件,可以免费使用。而购买F5 BIG-IP、NetScaler等硬件负载均衡交换机则需要十多万至几十万人民币 。

  • 内置的健康检查功能:如果 Nginx Proxy 后端的某台 Web 服务器宕机了,不会影响前端访问。

节省带宽:支持 GZIP 压缩,可以添加浏览器本地缓存的 Header 头。

稳定性高:用于反向代理,宕机的概率微乎其微

Nginx启动

  • 到官网下载Windows版本,下载地址:http://nginx.org/en/download.html

  • 解压到磁盘任一目录(注意:nginx解压后的文件夹不能放在中文目录下。

  • 修改配置文件

  • 启动服务:

    •直接运行nginx.exe

Nginx服务器默认占用的是80端口号,而在window10中端口号80已经被其它的应用程序占用,所以这里可以修改一下Nginx的端口号,在conf目录下找到nginx.conf文件,该文件就是Nginx服务的配置文件,通过该配置文件可以修改Nginx的端口号,当然后期针对Nginx服务器的配置都是通过该文件完成的。

在这里,我将端口号修改成了:8081,所以在浏览器的地址栏中,输入:http://localhost:8081 可以打开默认的欢迎页面,表示Nginx服务启动成功。

下面我们要做的就是将我们的程序部署到Nginx中。

现在,我们可以将做好的网站页面拷贝到Nginx中的html目录中,然后在地址栏中输入:

http://localhost:8081/

就可以看到对应的页面了,然后单击菜单,发现可以进行留有的切换。当单击刷新按钮后,发现出现了404的错误。

原因,点击刷新按钮就会向服务器发送请求,而在服务端没有对应的文件,所以会出现404的错误。

下面我们需要对Nginx服务器进行配置,找到conf目录下的nginx.conf.

然后进行如下的配置:

 location / {
            root   html;
            index  index.html index.htm;
			try_files $uri $uri/ /index.html;
        }

当在地址栏中输入/的时候,会请求根目录也就是html目录中的index.html.

现在,我们又加了try_files配置,表示尝试访问文件。

$uri表示根据所请求的url地址查找对应文件,如果找到了返回,没有找到。

$uri作为目录,查找该目录下的index.html,如果找到就返回,没有找到,则直接返回html目录下面的index.html文件。

而现在我们已经将我们做好的页面拷贝到了html目录下面,所以直接将我们的页面返回了。

下面可以进行测试。

测试之前需要重新启动服务器。

打开cmd,然后定位到Nginx的目录,输入以下命令重新启动服务器。

nginx -s reload

这时的执行流程是:当单击浏览器中的刷新按钮后,会向服务器发送请求,服务接收到请求后,发现没有所访问的文件,但是,由于我们配置了try_files,所以会将html目录下面的index.html页面的内容返回,返回给浏览器后,浏览器会根据路由来进行处理,也就是查找对应组件进行渲染。

Logo

前往低代码交流专区

更多推荐