【Vue,微信】单页应用到底该用hash模式还是history模式

单页面中使用history和hash的区别及优劣,选择适合自己的路由模式。

【Vue,微信】单页应用到底该用hash模式还是history模式

By img Microanswer Create at:Dec 6, 2019, 11:56:55 AM 

Tags: vue 微信 hash history 路由 router

单页面中使用history和hash的区别及优劣,选择适合自己的路由模式。


重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

在前端发展迅速的时代里,一个网站应用由原来的多页面变成了现在的单页面,使用JavaScript技术提供在单页面内实现多页效果切换的单页应用程序,带来了前端模块化开发、便于后期管理维护的同时,也由于单页路由的问题同时催生出了很多其它各种各样的问题。本文就要来讨论一下关于单页面路由模式,到底是使用hash模式好,还是history模式好。

一、URL的格式

我们都知道,使用hash模式和history模式最直观的区别就是 url 上一个有#号,一个没有#号。为了搞清楚#号到底起了一个什么作用,我们先来看看URL格式是什么样的:

这是一个很常见的url,它有着3个部分:

  1. 协议 https
  2. 主机地址
  3. 资源路径

再看下面的URL:

它有所不同,后面通过#号指定了页面里具体的位置。你曾经在学习html的锚点时肯定见过它,是的,当你点击你页面上一个链接,而这个链接不是一个新的页面地址,是一个当前页面内的锚点,这时候你的URL就会变成这样。

一个有效定位资源的URL,其实就是URL的#号前面的内容。

对于URL格式的知识,本文只关注我们遇到的#号,所以只举例2个必要的。现在你知道了这个#号最初是用来干嘛的了,但现在URL里的#号,好像不单单是一个锚点的功能了。下面我们继续讨论。

1、‘#’ 号的用途

#号出现在URL里,它后面的内容表示在这个网页里面某个位置,当你打开一个带#号的页面时,浏览器会自动在页面里寻找id值或name值等于#号后面的内容的地方,并将内容自动滚动到屏幕里。知道这个还不够,同时你还要知道下面这些细节:

  1. 当发送http请求时如果链接里有#号,浏览器是不会把#号后面的内容带上的,意味着一个:http://*.cn/#name请求实际上请求的是:http://*.cn/
  2. 当你改变#号后面的内容时,页面是不会触发重新加载的,不过会新增一条访问历史记录。

二、路由模式

现在,我们对URL有了一个基本的了解了,该祭出单页面里面路由的两种模式来进行讨论了。先从hash模式讨论。

1、hash 路由模式

好巧不巧,如果使用hash模式作为我们单页面应用的路由模式,其URL里正好也会有一个**#**号,但我们页内却没有了一个与#号后面对应的dom元素了,取而代之的是我们路由管理器去处理#号后面的内容。通过上一小节的内容,我们可以从中了解到,这个#号为我们带来了这样一些特性:

  1. 切换页面会改变#号后面的内容。
  2. 自动添加浏览器历史记录。
  3. 浏览器并不会重新全部再加载一遍页面。

OHHHHHHHHH!,这样的特性加持,相当于极大的减少了客户端的流量开销,同时还满足了操作习惯,按返回按键可以按历史记录的方式一层一层回退。唯一不好的,你可能认为这个#号怎么看怎么碍眼。

2、history 路由模式

很多前端开发朋友都喜欢这个模式,正如大多数喜欢漂亮的人一样,这种模式下的URL则是一个几乎完美无缺的完整字符串,没有其他特殊字符,强迫症患者表示一切都被治愈了。诚然,它的确为我们带来了心理上不少的安慰。但你又不得不对他严肃起来。

history模式没有了**#**号,这直接受影响的是什么?当然是浏览器,页面是直接与浏览器接触的东西,浏览器发现你的URL是一个完整的字符串,他的任务就是将这个URL的资源下载下来。要小心,浏览器每当检测到你要进行页面跳转的时候,就会去加载其对应的资源,你保不齐新界面的一些公共资源是否是重新又从服务端下载了一遍,还是重复利用了已经下载好了的。为什么保不齐?就是因为history模式没有了#号,浏览器便会重新下载所有需要的资源。而由于没有了#号,下载资源时又会拿你前端的路径去请求这些资源,你必须在后台兼容未找到资源的时候使用默认的index资源,才能保证页面不会404。同时你又要小心,你在跳转过程中,由于是单页面应用,路由代码里可能会主动推送一条历史记录到浏览器,而我们进行的这次跳转也会被浏览器自动检测并加入历史记录,无论怎样,这都导致了我们历史记录栈里面的记录与本身我们的操作顺序并不能完全保证一致。导致了我们如果使用history模式在部分浏览器上表现的十分不满意,偶尔总会出现:跳转页面白屏闪烁、返回时全部退出或返回后还在当前页面、等等这些奇奇怪怪的问题。

为什么大多数单页面框架默认都是使用的hash模式而不是后者呢?权衡利弊,history模式除了没有了#号,反而还要带来一系列体验问题,而这些问题又是来自各种浏览器对单页面history模式的支持不近统一。

三、‘#’ 号带来的问题及解决

你对hash模式的痛恨误绝,想必大多数情况下是来自你在单页面里使用微信js、微信支付时遇到的各种奇葩问题,而当你把路由模式换成history这些问题又会迎刃而解。所以你爱上了history模式,发现它又美观又没问题。如果你真的这样了,那就真是。。。牺牲了用户体验了。

hash模式表示,出现这些问题,这个锅老子不背!为了证明自己的清白,hash模式站了出来,详细的介绍了hash模式下该以什么姿势来正确使用。

1、获取微信用户openid

获取微信openid的流程是符合auth2.0授权标准的,无非就是在我们的页面里打开一个授权页面,用户授权完了又会跳回到我们指定的页面。在这个过程中,有些人就会出现跳转回来的界面(又叫回调界面)并不是我们预期的界面了,甚至出现直接授权的时候就在提示参数错误了。这些问题的根本都不是因为hash模式,而是没有弄清楚或忘记了url添加参数时对特殊字符必须要进行urlEncode操作后才能放在url上。

a、进入授权界面

例如,我们有页面:https://test.microanswer.cn/#/usercenter,进入此界面时,发现没有查找到缓存的openid,需要进行微信openid获取,那我们就在这个界面里写了这样的代码:

// 指定授权完成后还是回到同一个界面。
var REDIRECT_URI = "https://test.microanswer.cn/#/usercenter";
var appId = "******";
window.location.href = 
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appId + "&redirect_uri="+ REDIRECT_URI +"&response_type=code&scope=SCOPE&state=STATE#wechat_redirect"

猛地一看,这个代码非常perfect,完美无缺,测试运行,爆出error。首先要明白一点,我们的参数REDIRECT_URI尽管它也是一个链接地址,但此处我们是要把它作为一个参数放在另一个链接地址上,它本质上也是一个字符串,这个字符串中的:/#这些字符都属于特殊字符范畴,如果不进行urlEncode转换,那么我们地址里的#号直接切断整个完整有效api,使你的授权直接失败。

所以我们必须要先对参数进行urlEncode,反正urlEncode就对了,修复后就像这样:

// 指定授权完成后还是回到同一个界面。
var REDIRECT_URI = "https://test.microanswer.cn/#/usercenter";
var appId = "******";
window.location.href = 
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + encodeURIComponent(appId) + "&redirect_uri="+ encodeURIComponent(REDIRECT_URI) +"&response_type=code&scope=SCOPE&state=STATE#wechat_redirect"

这样才是一个正确的导向授权页面的姿势!这样回调回来也会进入到正确对应的界面。

b、回调页面

当你欣喜若狂,正确的回调到了指定的界面上后,你跟着文档上说的去获取回调传过来的code参数和state参数的时候,你猛的一句:

var code = this.$router.query.code;

结果拿到一个undefind。瞬间懵逼的你最后把回调后的url进行alert才发现,TM回调回来后,链接变成了下面这样:

"https://test.microanswer.cn/?code=weg123ku23frth2355csx&state=STATE#/usercenter";

搞半天参数跑到了#号前面去了,可这是为什么呢?要弄清它是为啥,不妨我们假设授权系统是我们自己开发的,那么下面模仿授权时做的操作:

// 第一步:拿到传递进来的参数,urlDecode解析解出原文
var REDIRECT_URI = decodeURIComponent(getParam('redirect_uri'));
var STATE        = decodeURIComponent(getParam('state'));

// 第二步:发现这个url里面有#号
if (REDIRECT_URI.has("#")) {
    // 由于 # 号前面才是有效资源URL,而后面只是一个定位符,
    // 因此我们但凡看到有#时,只操作#号前面的。
    var arrs = REDIRECT_URI.split("#");
    realUrl = arrs[0];
    urlEnd  = arrs[1];
}

// 第三步:执行用户授权操作
var result = askUser();

// 第四步:产生结果并回调
if (result) {
    // 产生一个code并使用此code回调
    redirct(realUrl + "?code=" + getNewAuthCode() + "&state=" + STATE + "#" + urlEnd);
}

当你读完上面的流程你就会发现,做为一个新的系统,我并不知道对方的系统的逻辑,而对于#号的规范我知道,#号后面的内容我不应该关心也不能去关心,所以只需要处理#号前面的就好了,处理完了最后你#号后面是什么,我还给你怎么样传过来。

现在你明白了,把问题回归我们自己的代码上,当我们发现回调参数在#号前面时也不要难过,你只需要同样通过#号截取前面部分,使用你喜欢的方式把code和state参数解析出来就可以继续业务了。

2、进行微信js的config校验

当你在页面内希望使用微信js提供的方法时,首要前提是要先进行config,而config需要你传递一个url,这个url只需要你的有效资源URL部分就好了,也就是#号前面的内容,通常只需要把window.location.href.split("#")[0]得到的结果传上去就好了,而有些时候,就比如刚刚从获取openid回调过来,#号前面还跟了一大堆参数。都不要慌,一切都在控制范围内,有这些参数就让他有吧,这些参数只是一个参数,在js进行config的时候匹配的是资源路径,才不会管你的参数有与没有,只要你给的资源路径和你微信后台配置的匹配得上,就都会成功。

不过除了这一点需要注意,还有微信js在android平台和ios平台上有不一样的行为偏好,《【微信公众号开发】七、微信JS需要注意的坑》这篇文章详细讲述了这些不同之处,建议你去看看。

3、微信js分享

你以为你顺利通过了config这道关卡就可以高枕无忧的使用微信分享功能了?NO,no。当你在进行微信分享的时候,你可能又会为链接里面的#号而犯愁了,就像个毒瘤一样让你的分享出去的页面不是你希望的界面。这个问题其实不大,如果你都按照上面的步骤一点一点的去实现,到这里也不会有问题。下面是一个典型的分享案例。假如有这个界面https://www.microanswer.cn/#/home要进行分享:

// 假设config已经完成
// 直接进行分享
var url = window.location.href; // 要分享的页面直接就是当前页面,不用担心里面是否有#号,直接一把梭!
wx.onMenuShareAppMessage({
    title: "分享标题",
    desc: "分享描述",
    link: url,
    type: 'link'
});

由于这是直接传递给js方法,所以不需要再进行urlEncode,这样就已经ok了,不会出现你担心的问题。要出问题的那一步是发生在别人点击你分享的出去的消息的那一瞬间。

假设现在真有人点击进来了,而你分享的地址是https://www.microanswer.cn/#/home,当他点击进来的时候,这个url就会立即变成https://www.microanswer.cn/?from=singlemessage#/home这样的格式进入你分享的界面,这其实并没有问题,它多了一个参数只是为了你能够在页面里清楚这是通过什么方式进入的界面,并不会对你产生任何损失。

案例走一下

当你打开的页面是https://www.microanswer.cn/?code=asfe354few1ca3df&state=STATE#/home这样时,进行微信js的config操作使用的url是:https://www.microanswer.cn/?code=asfe354few1ca3df&state=STATE,取#号前面嘛,也不用处理所以就这么长一串。但是放心没有问题,这时候你又要分享了,你使用了这个url:https://www.microanswer.cn/?code=asfe354few1ca3df&state=STATE#/home进行分享。但是code这东西是属于客户个人的,不能分享出去以免出现差错,所以还可以对这个分享出去的url里的参数进行删减,最终删减成这样https://www.microanswer.cn/?state=STATE#/home,然后把这个url传给分享方法,它也是可以分享成功了。有人点击你分享出去的消息进入页面,这时候页面的url是https://www.microanswer.cn/?state=STATE&from=singlemessage#/home,依然也是没有任何问题。

4、微信js发起支付

支付这一块算是安全要求非常高的了,但是hash模式表示我一样能够搞定。要能够发起微信支付,必须要现在微信支付商户后台配置之支付目录,是精确到目录的配置,下面就是配置方式:

  1. 对于页面https://www.microanswer.cn/#/product如果要进行支付,则你应该配置:https://www.microanswer.cn/#/
  2. 对于页面https://www.microanswer.cn/index.html#/product如果要进行支付,则你应该配置: https://www.microanswer.cn/index.html#/
  3. 如果你的页面里有些url即有index.html,有些又只有一个单独的#,那你就干脆把: https://www.microanswer.cn/https://www.microanswer.cn/#/https://www.microanswer.cn/index.html#/全部配上去。

配好后,就可以放心的进行支付了。

四、总结

其实各种各样的问题出现并不是hash模式路由导致的,而是对#号在这其中充当了一个什么样的角色,浏览器如何根据#号来处理相应的资源加载。只要开发过程中各项数据参数的传递都严格按照规范协议进行,就并不会出现各种各样的问题。所以最终,使用hash模式,浏览器可以更优雅的调度页内各个资源,用户体验更好,而用户很少关心链接上有没有#号,只是我们开发者自己认为没有#号显得逼格视乎更高,却让浏览器对你的项目识别变得模糊从而失去了更好的用户体验。

Full text complete, Reproduction please indicate the source. Help you? Not as good as one:
Comment(Comments need to be logged in. You are not logged in.)
You need to log in before you can comment.

Comments (0 Comments)