亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

詳解express使用vue-router的history踩坑

2024-05-06 16:51:49
字體:
來源:轉載
供稿:網友

vue-router 默認 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,于是當 URL 改變時,頁面不會重新加載。

如果不想要很丑的 hash,我們可以用路由的 history 模式,這種模式充分利用 history.pushState API 來完成 URL 跳轉而無須重新加載頁面。

當你使用 history 模式時,URL 就像正常的 url,例如 yoursite.com/user/id,也好看…

個人理解

上面是官方的解釋,文檔的一貫風格,只給懂的人看。兩年前我比現在還菜的時候,看了這段話表示他在說個錘子,直接跳過了。

我不講:hammer:,直接舉:chestnut:

一般的我們把項目放到服務器上,路由都是在服務器中設置的。

比如網站 https://www.text.com/ 中 admin目錄下有一個 login.html 的頁面。當用戶輸入 https://www.text.com/admin/login ,先解析 www.text.com 域名部分得到服務器 ip 和 端口號,根據 ip 和 端口號找到對應的服務器中的對應的程序,然后在程序解析 /admin/login 路徑知道了你要找的是 admin 目錄下的 login.html 頁面,然后就返回給你這個頁面。

這是正常的方式,服務器控制一個路由指向一個頁面的文件(不考慮重定向的情況),這樣我們的項一般有多少個頁面就有多少個 html 文件。

而 vue 中,我們打包好的文件其實是只有一個 index.html ,所有的行為都是在這一個頁面上完成。用戶的所有的路由其實都是在請求 index.html 頁面。

假設承載 vue 項目 index.html 也是在 admin 目錄下,vue 項目中也有一個 login 頁面,那對應的url就是 https://www.text.com/admin/#/login 。

這個 url 由三部分組成,是 www.text.com 是域名, /admin 是項目所在目錄,和上面一樣這個解析工作是由服務器完成的,服務器解析出 /admin 的路由,就返回給你 index.html 。 /#/login 是 vue-router 模擬的路由,因為頁面所有的跳轉 vue 都是在 index.html 中完成的,所以加上 # 表示頁內切換。假設切換到 home 頁面,對應的 html 文件還是 index.html ,url 變成 https://www.text.com/admin/#/home ,vue-router 判斷到 /#/home 的改變而改變了頁面 dom 元素,從而給用戶的感覺是頁面跳轉了。這就是 hash 模式。

那我們就知道了,正常的 url 和 hash 模式的區別,頁面的 js 代碼沒辦法獲取到服務器判斷路由的行為,所以只能用這種方式實現路由的功能。

而 history 模式就是讓 vue 的路由和正常的 url 一樣,至于怎么做下文會說到。

為什么需要實現

說怎么做之前,先說說為什么需要 history 模式。官方文檔說了,這樣比較好看。emmmmmm,對于直接面向消費者的網站好看這個確實是個問題,有個 /# 顯得不夠大氣。對于企業管理的 spa 這其實也沒什么。

所以除了好看之外,history 模式還有其他優勢。

我們知道,如果頁面使用錨點,就是一個 <a> 標簽, <a href='#mark1'></a> ,點擊之后如果頁面中有 id 為 mark1 的標簽會自動滾動到對應的標簽,而 url 后面會加上 #mark .

問題就出在這里,使用 hash 模式, #mark 會替換掉 vue-router 模擬的路由。比如這個 <a> 標簽是在上面說的 login 頁面,點擊之后 url 會從 https://www.text.com/admin/#/login 變成 https://www.text.com/admin/#/mark 。wtf???正??磥韱栴}不大,錨點滾動嘛,實在不行可以 js 模擬,但是因為我要實現 markdown 的標題導航功能,這個功能是插件做好的,究竟該插件還是用 history 。 權衡利弊下還是使用 history 模式工作量小,而且更美。

怎么做

既然知道是什么,為什么,下面就該研究怎么做了。

官方文檔里有“詳盡”的說明,其實這事兒本來不難,原理也很簡單。通過上文我們知道 vue-router 采用 hash 模式最大的原因在于所有的路由跳轉都是 js 模擬的,而 js 無法獲取服務器判斷路由的行為,那么就需要服務器的配合。原理就是無論用戶輸入的路由是什么全都指向 index.html 文件,然后 js 根據路由再進行渲染。

按照官方的做法,前端 router 配置里面加一個屬性,如下

const router = new VueRouter({ mode: 'history', routes: [...]})

后端的我不一一贅述,我用的是express,所以直接用了 connect-history-api-fallback 中間件。(中間件地址 https://github.com/bripkens/connect-history-api-fallback)

const history = require('connect-history-api-fallback')app.use(history({  rewrites: [    {      from: /^//.*$/,      to: function (context) {        return "/";      }    },  ]}));app.get('/', function (req, res) {  res.sendFile(path.join(process.cwd(), "client/index.html"));});app.use(  express.static(    path.join(process.cwd(), "static"),    {      maxAge: 0,//暫時關掉cdn    }  ));

坑1

按道理來說這樣就沒問題了,然鵝放到服務器里面之后,開始出幺蛾子了。靜態文件加載的時候接口返回都是

We're sorry but client doesn't work properly without JavaScript enabled. Please enable it to continue.

看著字面意思,說我的項目(項目名client)沒有啟用 JavaScript ,莫名其妙完全不能理解。于是乎仔細比對控制臺 responses headers 和request headers ,發現了一些貓膩,請求頭的 accept 和響應頭的 content-type 對不上,請求 css 文件請求頭的 accept 是text/css,響應頭的 content-type 是 text/html。這個不應該請求什么響應什么嗎,我想要崔鶯鶯一樣女子做老婆,給我個杜十娘也認了,結果你給我整個潘金蓮讓我咋整。

完全不知道到底哪里出了問題,google上面也沒有找到方法。開始瞎琢磨,既然對不上,那就想我手動給對上行不行。在express.static 的 setHeaders 里面檢查讀取文件類型,然后根據文件類型手動設置mime type,我開始佩服我的機智。

app.use(  express.static(    path.join(process.cwd(), "static"),    {      maxAge: 0,      setHeaders(res,path){        // 通過 path 獲取文件類型,設置對應文件的 mime type。      }    }  ));

緩存時間設置為0,關掉CDN... 一頓操作, 發現不執行 setHeaders 里面的方法。這個時候已經晚上 11 點了,我已經絕望了,最后一次看了一遍 connect-history-api-fallback 的文檔,覺得 htmlAcceptHeaders 這個配置項這么違和,其他的都能明白啥意思,就這個怎么都不能理解,死馬當活馬醫扔進代碼試試,居然成了。

const history = require('connect-history-api-fallback')app.use(history({  htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']  rewrites: [    {      from: /^//.*$/,      to: function (context) {        return "/";      }    },  ]}));

到底誰寫的文檔,靜態文件的 headers 的 accepts 和 htmlAcceptHeaders 有什么關系。咱也不知道,咱也沒地方問。這事兒耽誤了我大半天的時間,不研究透了心里不舒服。老規矩,看 connect-history-api-fallback 源碼。

'use strict';var url = require('url');exports = module.exports = function historyApiFallback(options) { options = options || {}; var logger = getLogger(options); return function(req, res, next) {  var headers = req.headers;  if (req.method !== 'GET') {   logger(    'Not rewriting',    req.method,    req.url,    'because the method is not GET.'   );   return next();  } else if (!headers || typeof headers.accept !== 'string') {   logger(    'Not rewriting',    req.method,    req.url,    'because the client did not send an HTTP accept header.'   );   return next();  } else if (headers.accept.indexOf('application/json') === 0) {   logger(    'Not rewriting',    req.method,    req.url,    'because the client prefers JSON.'   );   return next();  } else if (!acceptsHtml(headers.accept, options)) {   logger(    'Not rewriting',    req.method,    req.url,    'because the client does not accept HTML.'   );   return next();  }  var parsedUrl = url.parse(req.url);  var rewriteTarget;  options.rewrites = options.rewrites || [];  for (var i = 0; i < options.rewrites.length; i++) {   var rewrite = options.rewrites[i];   var match = parsedUrl.pathname.match(rewrite.from);   if (match !== null) {    rewriteTarget = evaluateRewriteRule(parsedUrl, match, rewrite.to, req);    if(rewriteTarget.charAt(0) !== '/') {     logger(      'We recommend using an absolute path for the rewrite target.',      'Received a non-absolute rewrite target',      rewriteTarget,      'for URL',      req.url     );    }    logger('Rewriting', req.method, req.url, 'to', rewriteTarget);    req.url = rewriteTarget;    return next();   }  }  var pathname = parsedUrl.pathname;  if (pathname.lastIndexOf('.') > pathname.lastIndexOf('/') &&    options.disableDotRule !== true) {   logger(    'Not rewriting',    req.method,    req.url,    'because the path includes a dot (.) character.'   );   return next();  }  rewriteTarget = options.index || '/index.html';  logger('Rewriting', req.method, req.url, 'to', rewriteTarget);  req.url = rewriteTarget;  next(); };};function evaluateRewriteRule(parsedUrl, match, rule, req) { if (typeof rule === 'string') {  return rule; } else if (typeof rule !== 'function') {  throw new Error('Rewrite rule can only be of type string or function.'); } return rule({  parsedUrl: parsedUrl,  match: match,  request: req });}function acceptsHtml(header, options) { options.htmlAcceptHeaders = options.htmlAcceptHeaders || ['text/html', '*/*']; for (var i = 0; i < options.htmlAcceptHeaders.length; i++) {  if (header.indexOf(options.htmlAcceptHeaders[i]) !== -1) {   return true;  } } return false;}function getLogger(options) { if (options && options.logger) {  return options.logger; } else if (options && options.verbose) {  return console.log.bind(console); } return function(){};}

這個代碼還真是通俗易懂,就不去一行行分析了(其實是我懶)。直接截取關鍵代碼:

else if (!acceptsHtml(headers.accept, options)) {   logger(    'Not rewriting',    req.method,    req.url,    'because the client does not accept HTML.'   );   return next();  }
function acceptsHtml(header, options) { //在這里 options.htmlAcceptHeaders = options.htmlAcceptHeaders || ['text/html', '*/*']; for (var i = 0; i < options.htmlAcceptHeaders.length; i++) {  if (header.indexOf(options.htmlAcceptHeaders[i]) !== -1) {   return true;  } } return false;}

前一段代碼,如果 acceptsHtml 函數返回 false,說明瀏覽器不接受 html 文件,跳過執行 next(),否則繼續執行。

后一段代碼, acceptsHtml 函數內部設置 htmlAcceptHeaders 的默認值是 'text/html', '*/*' 。判斷請求頭的accept,如果匹配上說明返回true,否則返回false。直接用默認值接口不能正常返回 css 和 js, 改成 'text/html', 'application/xhtml+xml' 就能運行了。這就奇了怪了,htmlAcceptHeaders 為什么會影響 css 和 js。太晚了,不太想糾結了,簡單粗暴把源碼摳出來直接放到項目里面跑一下,看看到底發生了什么。

function acceptsHtml(header, options) {  options.htmlAcceptHeaders = options.htmlAcceptHeaders || ['text/html', '*/*'];  console.log("header", header);  console.log("htmlAcceptHeaders", options.htmlAcceptHeaders);  for (var i = 0; i < options.htmlAcceptHeaders.length; i++) {    console.log("indexOf", header.indexOf(options.htmlAcceptHeaders[i]));    if (header.indexOf(options.htmlAcceptHeaders[i]) !== -1) {      return true;    }  }  return false;}

設置 htmlAcceptHeaders 值為 'text/html', 'application/xhtml+xml'

header text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3htmlAcceptHeaders [ 'text/html', 'application/xhtml+xml' ]indexOf 0header text/css,*/*;q=0.1htmlAcceptHeaders [ 'text/html', 'application/xhtml+xml' ]indexOf -1indexOf -1

不設置 htmlAcceptHeaders

header text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3htmlAcceptHeaders [ 'text/html', '*/*' ]indexOf 0header application/signed-exchange;v=b3;q=0.9,*/*;q=0.8htmlAcceptHeaders [ 'text/html', '*/*' ]indexOf -1indexOf 39

這時候我突然茅塞頓開,htmlAcceptHeaders 這個屬性過濾 css 和 js 文件,如果用默認的 'text/html', '*/*' 屬性,css 和 js 文件都會被匹配成 html 文件,然后一陣處理導致響應頭的 mime 文件類型變成 text/html 導致瀏覽器無法解析。

原來不是寫文檔的人邏輯有問題,而是他是個懶人,不想解釋太多,我是個蠢人不能一下子理解他的“深意”。

坑2

還有一點要注意,就是路由名稱的設定。還是這個URL https://www.text.com/admin/login ,服務器把所有 /admin 的路由都指向了 vue 的 index.html 文件,hash模式下我們的路由這么配置的路由

const router = new VueRouter({ routes: [{    path: "/login",    name: "login",    component: login  }]})

這時我們改成history模式

const router = new VueRouter({ mode: 'history', routes: [{    path: "/login",    name: "login",    component: login  }]})

打開 url https://www.text.com/admin/login 會發現自動跳轉到 https://www.text.com/login ,原因就是 /admin 的路由都指向了 vue 的 index.html 文件之后,js 根據我們的代碼把url改成了 https://www.text.com/login ,如果我們不刷新頁面沒有任何問題,因為頁面內所有的跳轉還是 vue-router 控制, index.html 這個文件沒變。但是如果刷新頁面那就會出問題,服務器重新判斷 /login 路由對應的文件。因此使用 history 模式時前端配置 vue-router 時也需要考慮后臺的項目所在目錄。

比如上面的例子應該改為,這樣可以避免這種情況的問題

const router = new VueRouter({ mode: 'history', routes: [{    path: "/admin/login",    name: "login",    component: login  }]})

參考鏈接

https://router.vuejs.org/zh/guide/essentials/history-mode.html#后端配置例子

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲色图综合网| 国产成人涩涩涩视频在线观看| 日韩二区三区在线| 成人字幕网zmw| 91社区国产高清| 一区二区三区视频观看| 日韩精品在线视频| 成人黄色在线播放| 亚州欧美日韩中文视频| 国产精品电影观看| 精品少妇v888av| 国产精品自产拍在线观看中文| 久久亚洲精品一区二区| 成人情趣片在线观看免费| 国产香蕉精品视频一区二区三区| 国产精品久久久久久久久久东京| 日韩av免费在线看| zzijzzij亚洲日本成熟少妇| 国产亚洲成av人片在线观看桃| 欧美激情亚洲国产| 96pao国产成视频永久免费| 欧美在线一级视频| 亚洲区一区二区| 亚洲国产精品成人一区二区| 国产视频一区在线| 欧美精品亚州精品| 欧美日韩午夜视频在线观看| 色综合久久中文字幕综合网小说| 九九视频直播综合网| 岛国视频午夜一区免费在线观看| 成人免费福利在线| 亚洲国产三级网| 久久精品国产一区二区三区| 国产精品美女在线| 精品视频偷偷看在线观看| 亚洲欧洲美洲在线综合| 美女久久久久久久| 亚洲精品一区二区久| 亚洲理论在线a中文字幕| 日韩成人xxxx| 国产98色在线| 午夜欧美大片免费观看| 久久高清视频免费| 午夜精品久久久久久久男人的天堂| 欧美大片在线看| 欧美国产第二页| 一区二区三区日韩在线| 午夜精品在线观看| 成人精品一区二区三区| 欧美国产一区二区三区| 欧美专区福利在线| 欧美日韩国产第一页| 国产精品日韩电影| 成人欧美一区二区三区在线湿哒哒| 精品欧美国产一区二区三区| 国产精品一区二区av影院萌芽| 最近的2019中文字幕免费一页| 欧美一级免费视频| 成人av资源在线播放| 欧美精品一区三区| 日韩电影在线观看永久视频免费网站| 日韩av大片在线| 在线视频中文亚洲| 欧美激情第6页| 精品成人av一区| www.国产精品一二区| 狠狠久久亚洲欧美专区| 欧美另类交人妖| 91久久在线视频| 亚洲福利影片在线| 久久久99免费视频| 国语自产精品视频在免费| 国产精品入口夜色视频大尺度| 亚洲精品美女在线观看| 91成人免费观看网站| 欧美风情在线观看| 欧美午夜女人视频在线| 国产精品网址在线| 亚洲国产精品久久久久| 亚洲无限av看| 日本伊人精品一区二区三区介绍| 国产在线久久久| 亚洲性av在线| 日韩精品日韩在线观看| 亚洲第一福利网站| 日韩电视剧免费观看网站| 亚洲人成电影在线| 欧美xxxx做受欧美| 精品久久久久久久久久久久| 午夜精品一区二区三区在线播放| 精品国产一区二区三区久久久狼| 91精品久久久久久久久不口人| 97超碰色婷婷| 色黄久久久久久| 亚洲天堂男人的天堂| 欧美国产视频一区二区| 在线看欧美日韩| 日韩精品视频中文在线观看| 亚洲美女视频网站| 欧美亚洲国产视频| 奇米四色中文综合久久| 日韩电影大全免费观看2023年上| 777精品视频| 欧美刺激性大交免费视频| 欧美成在线观看| 黄色成人av网| 亚洲欧美日韩国产中文专区| 日韩av片永久免费网站| 久久久精品999| 欧美小视频在线| 亚洲人成欧美中文字幕| 亚洲免费成人av电影| 中文字幕日韩有码| 成人黄色影片在线| 亚洲成年网站在线观看| 亚洲成年网站在线观看| 亚洲综合中文字幕在线| 91精品久久久久久久久久久| 亚洲欧美精品在线| 国产99久久精品一区二区| 欧美日韩在线第一页| 91av在线免费观看视频| 欧美中文字幕精品| 久久人体大胆视频| 国产精品视频专区| 亚洲男人的天堂在线| 欧美性少妇18aaaa视频| 欧美精品激情blacked18| 久久久久久999| 国产精品电影在线观看| 欧美性开放视频| 色综合久久天天综线观看| 日韩av免费在线播放| 国产一区二区三区在线观看网站| 日韩精品视频在线观看网址| 在线看日韩欧美| 亚洲国产古装精品网站| 538国产精品一区二区免费视频| 日韩av网站在线| 久久精品电影一区二区| www.美女亚洲精品| 日韩高清电影好看的电视剧电影| 亚洲一区二区三区乱码aⅴ| 国产精品电影一区| 国产成人精品电影久久久| 精品久久中文字幕| 欧美日韩美女视频| 亚洲人精选亚洲人成在线| 777精品视频| 国产精品免费久久久| 亚洲国产天堂久久综合网| 亚洲亚裔videos黑人hd| 亚洲国产精品久久久久| 国产亚洲精品综合一区91| 亚洲人线精品午夜| 中文字幕日韩电影| 日本最新高清不卡中文字幕| 日本中文字幕不卡免费| 亚洲人成网站色ww在线| 国产精品7m视频| 亚洲精品白浆高清久久久久久| 国产婷婷97碰碰久久人人蜜臀| 欧美激情区在线播放| 91老司机精品视频|