欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2019年06月05日 08:18:17   作者:FrancisXu  
這篇文章主要介紹了express 使用 vue-router 的 history 踩坑,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

vue-router 默認(rèn) hash 模式 —— 使用 URL 的 hash 來(lái)模擬一個(gè)完整的 URL,于是當(dāng) URL 改變時(shí),頁(yè)面不會(huì)重新加載。

如果不想要很丑的 hash,我們可以用路由的 history 模式,這種模式充分利用 history.pushState API 來(lái)完成 URL 跳轉(zhuǎn)而無(wú)須重新加載頁(yè)面。

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

個(gè)人理解

上面是官方的解釋?zhuān)臋n的一貫風(fēng)格,只給懂的人看。兩年前我比現(xiàn)在還菜的時(shí)候,看了這段話(huà)表示他在說(shuō)個(gè)錘子,直接跳過(guò)了。

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

一般的我們把項(xiàng)目放到服務(wù)器上,路由都是在服務(wù)器中設(shè)置的。

比如網(wǎng)站 https://www.text.com/ 中 admin目錄下有一個(gè) login.html 的頁(yè)面。當(dāng)用戶(hù)輸入 https://www.text.com/admin/login ,先解析 www.text.com 域名部分得到服務(wù)器 ip 和 端口號(hào),根據(jù) ip 和 端口號(hào)找到對(duì)應(yīng)的服務(wù)器中的對(duì)應(yīng)的程序,然后在程序解析 /admin/login 路徑知道了你要找的是 admin 目錄下的 login.html 頁(yè)面,然后就返回給你這個(gè)頁(yè)面。

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

而 vue 中,我們打包好的文件其實(shí)是只有一個(gè) index.html ,所有的行為都是在這一個(gè)頁(yè)面上完成。用戶(hù)的所有的路由其實(shí)都是在請(qǐng)求 index.html 頁(yè)面。

假設(shè)承載 vue 項(xiàng)目 index.html 也是在 admin 目錄下,vue 項(xiàng)目中也有一個(gè) login 頁(yè)面,那對(duì)應(yīng)的url就是 https://www.text.com/admin/#/login 。

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

那我們就知道了,正常的 url 和 hash 模式的區(qū)別,頁(yè)面的 js 代碼沒(méi)辦法獲取到服務(wù)器判斷路由的行為,所以只能用這種方式實(shí)現(xiàn)路由的功能。

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

為什么需要實(shí)現(xiàn)

說(shuō)怎么做之前,先說(shuō)說(shuō)為什么需要 history 模式。官方文檔說(shuō)了,這樣比較好看。emmmmmm,對(duì)于直接面向消費(fèi)者的網(wǎng)站好看這個(gè)確實(shí)是個(gè)問(wèn)題,有個(gè) /# 顯得不夠大氣。對(duì)于企業(yè)管理的 spa 這其實(shí)也沒(méi)什么。

所以除了好看之外,history 模式還有其他優(yōu)勢(shì)。

我們知道,如果頁(yè)面使用錨點(diǎn),就是一個(gè) <a> 標(biāo)簽, <a href='#mark1'></a> ,點(diǎn)擊之后如果頁(yè)面中有 id 為 mark1 的標(biāo)簽會(huì)自動(dòng)滾動(dòng)到對(duì)應(yīng)的標(biāo)簽,而 url 后面會(huì)加上 #mark .

問(wèn)題就出在這里,使用 hash 模式, #mark 會(huì)替換掉 vue-router 模擬的路由。比如這個(gè) <a> 標(biāo)簽是在上面說(shuō)的 login 頁(yè)面,點(diǎn)擊之后 url 會(huì)從 https://www.text.com/admin/#/login 變成 https://www.text.com/admin/#/mark 。wtf???正常看來(lái)問(wèn)題不大,錨點(diǎn)滾動(dòng)嘛,實(shí)在不行可以 js 模擬,但是因?yàn)槲乙獙?shí)現(xiàn) markdown 的標(biāo)題導(dǎo)航功能,這個(gè)功能是插件做好的,究竟該插件還是用 history 。 權(quán)衡利弊下還是使用 history 模式工作量小,而且更美。

怎么做

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

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

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

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,//暫時(shí)關(guān)掉cdn
    }
  )
);

坑1

按道理來(lái)說(shuō)這樣就沒(méi)問(wèn)題了,然鵝放到服務(wù)器里面之后,開(kāi)始出幺蛾子了。靜態(tài)文件加載的時(shí)候接口返回都是

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

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

完全不知道到底哪里出了問(wèn)題,google上面也沒(méi)有找到方法。開(kāi)始瞎琢磨,既然對(duì)不上,那就想我手動(dòng)給對(duì)上行不行。在express.static 的 setHeaders 里面檢查讀取文件類(lèi)型,然后根據(jù)文件類(lèi)型手動(dòng)設(shè)置mime type,我開(kāi)始佩服我的機(jī)智。

app.use(
  express.static(
    path.join(process.cwd(), "static"),
    {
      maxAge: 0,
      setHeaders(res,path){
        // 通過(guò) path 獲取文件類(lèi)型,設(shè)置對(duì)應(yīng)文件的 mime type。
      }
    }
  )
);

緩存時(shí)間設(shè)置為0,關(guān)掉CDN... 一頓操作, 發(fā)現(xiàn)不執(zhí)行 setHeaders 里面的方法。這個(gè)時(shí)候已經(jīng)晚上 11 點(diǎn)了,我已經(jīng)絕望了,最后一次看了一遍 connect-history-api-fallback 的文檔,覺(jué)得 htmlAcceptHeaders 這個(gè)配置項(xiàng)這么違和,其他的都能明白啥意思,就這個(gè)怎么都不能理解,死馬當(dāng)活馬醫(yī)扔進(jìn)代碼試試,居然成了。

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

到底誰(shuí)寫(xiě)的文檔,靜態(tài)文件的 headers 的 accepts 和 htmlAcceptHeaders 有什么關(guān)系。咱也不知道,咱也沒(méi)地方問(wèn)。這事兒耽誤了我大半天的時(shí)間,不研究透了心里不舒服。老規(guī)矩,看 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(){};
}

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

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 函數(shù)返回 false,說(shuō)明瀏覽器不接受 html 文件,跳過(guò)執(zhí)行 next(),否則繼續(xù)執(zhí)行。

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

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;
}

設(shè)置 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=b3
htmlAcceptHeaders [ 'text/html', 'application/xhtml+xml' ]
indexOf 0
header text/css,*/*;q=0.1
htmlAcceptHeaders [ 'text/html', 'application/xhtml+xml' ]
indexOf -1
indexOf -1

不設(shè)置 htmlAcceptHeaders

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

這時(shí)候我突然茅塞頓開(kāi),htmlAcceptHeaders 這個(gè)屬性過(guò)濾 css 和 js 文件,如果用默認(rèn)的 'text/html', '*/*' 屬性,css 和 js 文件都會(huì)被匹配成 html 文件,然后一陣處理導(dǎo)致響應(yīng)頭的 mime 文件類(lèi)型變成 text/html 導(dǎo)致瀏覽器無(wú)法解析。

原來(lái)不是寫(xiě)文檔的人邏輯有問(wèn)題,而是他是個(gè)懶人,不想解釋太多,我是個(gè)蠢人不能一下子理解他的“深意”。

坑2

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

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

這時(shí)我們改成history模式

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

打開(kāi) url https://www.text.com/admin/login 會(huì)發(fā)現(xiàn)自動(dòng)跳轉(zhuǎn)到 https://www.text.com/login ,原因就是 /admin 的路由都指向了 vue 的 index.html 文件之后,js 根據(jù)我們的代碼把url改成了 https://www.text.com/login ,如果我們不刷新頁(yè)面沒(méi)有任何問(wèn)題,因?yàn)轫?yè)面內(nèi)所有的跳轉(zhuǎn)還是 vue-router 控制, index.html 這個(gè)文件沒(méi)變。但是如果刷新頁(yè)面那就會(huì)出問(wèn)題,服務(wù)器重新判斷 /login 路由對(duì)應(yīng)的文件。因此使用 history 模式時(shí)前端配置 vue-router 時(shí)也需要考慮后臺(tái)的項(xiàng)目所在目錄。

比如上面的例子應(yīng)該改為,這樣可以避免這種情況的問(wèn)題

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

參考鏈接

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 安裝node.js和npm的一些常見(jiàn)報(bào)錯(cuò)

    安裝node.js和npm的一些常見(jiàn)報(bào)錯(cuò)

    NVM(Node?Version?Manager)是一個(gè)用于在同一機(jī)器上同時(shí)安裝并管理多個(gè)Node.js版本的工具,這篇文章主要給大家介紹了關(guān)于安裝node.js和npm的一些常見(jiàn)報(bào)錯(cuò),需要的朋友可以參考下
    2023-06-06
  • Node.js處理I/O數(shù)據(jù)之使用Buffer模塊緩沖數(shù)據(jù)

    Node.js處理I/O數(shù)據(jù)之使用Buffer模塊緩沖數(shù)據(jù)

    這篇文章介紹了Node.js使用Buffer模塊緩沖數(shù)據(jù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • node中實(shí)現(xiàn)刪除目錄的幾種方法

    node中實(shí)現(xiàn)刪除目錄的幾種方法

    這篇文章主要介紹了node中實(shí)現(xiàn)刪除目錄的幾種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Node.js 中正確使用 async/await 與 Promise 對(duì)象配合(操作方法)

    Node.js 中正確使用 async/await 與 Promise 

    在Node.js中,async/await是ES2017引入的一種更簡(jiǎn)潔的處理異步操作的方式,它基于Promise來(lái)進(jìn)行編寫(xiě),使得異步代碼看起來(lái)更像同步代碼,易于理解和維護(hù),這篇文章主要介紹了Node.js 中正確使用 async/await 與 Promise 對(duì)象配合,需要的朋友可以參考下
    2024-07-07
  • Node.JS事件的綁定與觸發(fā)示例詳解

    Node.JS事件的綁定與觸發(fā)示例詳解

    Node中的事件模型就是我們常見(jiàn)的訂閱發(fā)布模式,Nodejs核心API都采用異步事件驅(qū)動(dòng),所有可能觸發(fā)事件的對(duì)象都是一個(gè)繼承自EventEmitter類(lèi)的子類(lèi)實(shí)例對(duì)象,這篇文章主要給大家介紹了關(guān)于Node.JS事件的綁定與觸發(fā)事件的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • Nodejs中調(diào)用系統(tǒng)命令、Shell腳本和Python腳本的方法和實(shí)例

    Nodejs中調(diào)用系統(tǒng)命令、Shell腳本和Python腳本的方法和實(shí)例

    這篇文章主要介紹了Nodejs中調(diào)用系統(tǒng)命令、Shell腳本和Python腳本的方法和實(shí)例,本文給出了利用子進(jìn)程調(diào)用系統(tǒng)命令、執(zhí)行系統(tǒng)命令、調(diào)用傳參數(shù)的shell腳本、調(diào)用python腳本的例子,需要的朋友可以參考下
    2015-01-01
  • 輕松創(chuàng)建nodejs服務(wù)器(2):nodejs服務(wù)器的構(gòu)成分析

    輕松創(chuàng)建nodejs服務(wù)器(2):nodejs服務(wù)器的構(gòu)成分析

    這篇文章主要介紹了輕松創(chuàng)建nodejs服務(wù)器(2):nodejs服務(wù)器的構(gòu)成分析,本文是對(duì)第一節(jié)中簡(jiǎn)單服務(wù)器的代碼進(jìn)行分析總結(jié),需要的朋友可以參考下
    2014-12-12
  • Node.js開(kāi)源應(yīng)用框架HapiJS介紹

    Node.js開(kāi)源應(yīng)用框架HapiJS介紹

    這篇文章主要介紹了Node.js開(kāi)源應(yīng)用框架HapiJS介紹,本文講解了HapiJS介紹、HapiJS安裝和項(xiàng)目配置和開(kāi)發(fā)實(shí)例等內(nèi)容,需要的朋友可以參考下
    2015-01-01
  • 深入解析Nodejs中的大文件讀寫(xiě)

    深入解析Nodejs中的大文件讀寫(xiě)

    這篇文章主要介紹了深入解析Nodejs中的大文件讀寫(xiě),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • Node.js環(huán)境下編寫(xiě)爬蟲(chóng)爬取維基百科內(nèi)容的實(shí)例分享

    Node.js環(huán)境下編寫(xiě)爬蟲(chóng)爬取維基百科內(nèi)容的實(shí)例分享

    WikiPedia平時(shí)在國(guó)內(nèi)不大好訪(fǎng)問(wèn)-- 所以用爬蟲(chóng)一次性把要看的東西都爬下來(lái)保存慢慢看還是比較好的XD 這里我們就來(lái)看一下Node.js環(huán)境下編寫(xiě)爬蟲(chóng)爬取維基百科內(nèi)容的實(shí)例分享
    2016-06-06

最新評(píng)論