《從零構建前後分離的web專案》實戰 - 慾善其事必先利其器 繼續打磨前端架構
上回還真的有同學提到了這個問題,感謝細心的你。@_noob

其實是沒任何問題的,只不過看起來違背了常見的結構,像是有問題。其實是上文為了照顧初學者,怕大家因為麻煩而放棄,並沒有一次性改的“看起來那麼複雜”,我們來填下坑。
為了照顧沒有實時跟著我連載的同學,每一章的程式碼單獨釋出在我的 Github 部落格,不進行覆蓋更新 [除非程式碼有錯誤進行修改],這樣避免了 2019 年小明同學望著前兩章和 Github 最終版本程式碼發呆 ( release 也不是特別友好)
我們把上文的 renderer/src 改成比較容易理解的 src/renderer ,這其實是一個程式設計習慣問題。
上文 renderer/src 的問題

eslint-config-alloy : 想寫出更規範的程式碼可以參考這個規則

下面進入本章正題
axios
使用了 vue 的你,發現 Vue 居然不能發請求,於是你 Google 了下,發現可以用 Vue-Resource。 你去問別人 Vue-Resource 怎麼樣,他說不要用 Vue-Resource,因為 Vue-Resource 官方已經停止維護了,你應該用 Axios、或者 fetch。但是我們想擁抱 ES6 排除掉了 ES5的fetch(當然也有ES6-fetch),這裡我們使用 Axios!
Tips
這裡呢也科普一下:什麼時候依賴需要放到 dependencies、什麼時候依賴需要放到 devDependencies:
devDependencies:顧名思義,僅在開發(dev)模式下如:webpack. 、 .loader、eslint、babel、打包後部署時完全用不到的、僅在開發需要 編譯、檢測、轉換 的放在這裡。 dependencies:例如:axios、chart、js-cookie、less、lodash、underscore等執行時的庫或工具類等相關依賴我們要放在這裡
不過基本不用擔心,官網都會提供 start 說明,但是我們要大概明白意思,不要機械般的 copy。
引入 Axios
- 直接玩最新的
2018-09-28 截圖npmjs.com
- 新增依賴
"dependencies": { "axios": "^0.18.0" } 複製程式碼

基於上一章內容,別忘了重新 npm i 下載一下
還記得我們自動生成的 vue 主頁面指令碼 main.js嗎?
封裝axios
我們在 src/renderer/utils
建立一個 request.js
在這個請求指令碼中,對 Axios 做一些必要的封裝,大概內容是用 攔截器 axios.interceptors 對請求和響應做些攔截,定義一下 API 的字首,處理一些常見的 HTTP 狀態碼。
- interceptors 文件

我儘可能的為大家寫了詳細的註釋。
// src/renderer/utils/request.js import axios from 'axios' //這裡一般指後端專案API的字首,例如 /baidu/*/*/1.api/mi/*/*/2.api const BASE_API = "" export function axiosIntercept(Vue, router) { const axiosIntercept = axios.create({ baseURL: BASE_API }) //http request 攔截器 一般用來在請求前塞一些全域性的配置、或開啟一些 css 載入動畫 axios.interceptors.request.use( (config) => { // 判斷是否存在token,如果存在的話,則每個http header都加上token // if (store.getters.accessToken) { //console.log(store.getters.accessToken) //config.headers.Authorization = `token ${store.getters.accessToken}`; // } //todo:載入動畫 //若有需求可以處理一下 post 亦或改變post傳輸格式 if (config.method === 'post') { }; return config; }, function (err) { return Promise.reject(err); }); //http response 攔截器 一般用來根據一些後端協議特殊返回值做一些處理,例如:許可權方面、404... 或關閉一些 css 載入動畫 axiosIntercept.interceptors.response.use(function (response) { // todo: 暫停載入動畫 return response; }, function (err) { //捕獲異常 if (err.response) { switch (err.response.status) { case 401: // do something 這裡我們寫完後端做好約束再完善 } } return Promise.reject(err); }); return axiosIntercept; } 複製程式碼
大家還記得我們用 vue-cli 生成的 vue 主頁尾本 main.js 吧,這裡我們需要對 Axios 和 Vue 做一個耦合。
// src/renderer/main.js import axios from 'axios' import { axiosIntercept } from './utils/request' // 將Axios擴充套件到Vue原型鏈中 Vue.prototype.$http = axiosIntercept(Vue) 複製程式碼
這樣我們在寫業務邏輯,直接在 Vue 的上下文中 使用 this.$http 來發送請求。既實現了攔截、又實現了狀態的共享。
知其然,知其所以然
- 這樣做的意義在哪?
節省程式碼量,讓程式碼更加易讀
- 為什麼?
擴充套件到原型鏈,使 Axios 執行時共享 Vue 原型鏈的內容,減少了很多指代 Vue 的臨時變數
- 舉個栗子
傳統情況
import axios from 'axios' new Vue({ data: { user: "" }, created: function () { //此時作用域在 Vue 上,快取起來,要依賴此變數 let _this = this; axios.get("/user/getUserInfo/" + userName).then(res => { if (res.data.code === 200) { //此時作用域在axios上,拿不到vue繫結的值,只能藉助剛才快取的_this上下文 _this.data.user = res.data.user } }); } }) 複製程式碼
代理之後
new Vue({ data: { user: "" }, created: function () { // axios 成為了 vue 的原型鏈一部分,共享vue狀態。 this.$http.get("/user/getUserInfo/" + userName).then(res => { if (res.data.code === 200) { //注意,axios回撥,應該儘量使用箭頭函式,可以繼承父類上下文,否則類似閉包,還是無法共享變數、 // 更優雅了一些 this.data.user = res.data.user } }); } }) 複製程式碼
不懂 prototype 可以翻翻我以前寫的文章
proxy
先簡單弄一下,為前後分離打個小鋪墊
webPack
- webPack 的別名
resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src/renderer'), } }, 複製程式碼
為了使用起來更加優雅,可以為每個常用的目錄都建立別名
resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src/renderer'), 'assets': resolve('src/renderer/assets'), 'components': resolve('src/renderer/components'), 'container': resolve('src/renderer/container'), 'utils': resolve('src/renderer/utils') } }, 複製程式碼
生產和開發的跨域問題

dev 是開發時啟動的指令 build 是預釋出時 webPack 打包的指令
假設筆者只是一個前端,通常呢,在開發除錯過程當中,無法避免需要與後端的同學進行 API 的對接,那也就難免會出現跨域問題。當然傳統 javaWeb 不需要跨域,(ip 域 埠 任何一個不同皆為跨域) 在 DEV 模式除錯中,我們都是儘量選擇前端環境規避跨域問題,而不會去額外搭建 nginx 或更改後端程式碼。
跨域只是針對 JavaScript 的,因為開發者認為瀏覽器上的指令碼是不安全的。
既然我們的 vue 專案是 node 全家桶,依靠 node、webPack 編譯 我們直接配置 node 的 proxyTable 作為開發的代理器,這樣最簡單,依次配置,團隊受益。
cnode 掘金 社群 API 舉例
cnodejs.org/api 上邊cnode 的 API 是可以隨意呼叫的,因為後端做了處理。
看看掘金的: ofollow,noindex">xiaoce-timeline-api-ms.juejin.im/v1/getListB… 請求一下,不出意外瀏覽器做了跨域報警。

哦,我們適配一下 node 代理
官方例子在這: vuejs-templates.github.io/webpack/pro…
擴充套件一下 proxyTable:
proxyTable: [{ //攔截所有v1開頭的xhr請求 context: ['/v1'], target: "https://xiaoce-timeline-api-ms.juejin.im", cookieDomainRewrite: { // 不用cookie }, changeOrigin: true,//重點,此處本地就會虛擬一個服務替我們接受或轉發請求 secure: false }], 複製程式碼
再次傳送請求。
- 愉快的拿到了資料

這樣,前後分離的專案可以這樣藉助 swagger 測試介面,不需要騷擾任何人。實現自己的業務邏輯,簡單實現一點。

程式碼:
// blog/index.vue <template> <div class="Blog"> <h1>{{ msg }}</h1> <div v-for="(blog,index) in blogList" v-bind:key="index"> <h3 > <a :href="`https://juejin.im/book/`+blog.id" > <span>{{blog.title}}</span> </a> </h3> </div> </div> </template> <script> export default { name: "Blog", data() { return { msg: "掘金小冊一覽", blogList: [] }; }, created() { this.getBlog(); }, methods: { getBlog() { this.$http.get("/v1/getListByLastTime?src=web&pageNum=1").then(res => { this.blogList = res.data.d; }); } } }; </script> 複製程式碼
釋出
- 掃盲
寫完程式碼之後部署到線上,是不會在線上 clone 程式碼之後 Npm run dev 的:laughing:,那樣會有太多太多的垃圾依賴,為使用者帶來了災難性的網路請求,通常藉助webPack打包之後釋出到伺服器的web服務當中。
執行
npm run build 複製程式碼

打包目錄是之前配置的webpack


好了,很多人直接雙擊 index.html 是不行的。
Tip: built files are meant to be served over an HTTP server. Opening index.html over file:// won't work.
需要 http 服務啟動,可以扔到本地或伺服器的 nginx、apache、tomcat等容器測試,我通常使用 python 啟動一個 http 服務來執行(指令碼地址)、當然,自己 ide 支援 http 啟動也可以。
生產中的跨域
生產當中,因為前端後端必然不同埠,避免跨域,通常使用 nginx 的正向/反向代理作為跨域的手段。(並非負載均衡,兩個概念)
server { listen80; server_namelocalhost; location ^~ /v1 { proxy_passhttps://xiaoce-timeline-api-ms.juejin.im;#代理。 proxy_set_headerX-Real-IP $remote_addr;#轉發客戶端真實IP proxy_set_headerX-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_headerHost $http_host; } } 複製程式碼
簡單配置一下就可以了,不多講,前端同學瞭解一下就可以了,nginx 能幹的事情還有很多。
API 的規範化
你是否為了找某一個業務的介面頭痛 你是否還在使用全域性搜尋找自己的介面 你是否某一個介面不同元件重複寫了多次 整理一下自己的介面吧,像上文的 router 一樣整齊的劃分吧。

/renderer 下建立一個 api 的資料夾

webpack.base.conf.js 新增一條 api 的別名,方便我們日後大量呼叫
'api': resolve('src/renderer/api') 複製程式碼
我們建立 /renderer/api/juejin.js
import axios from 'axios' let Api = Function() Api.prototype = { getBlog(page, fn) { axios.get(`/v1/getListByLastTime?src=web&pageNum=${page}`).then(res => { // if (res.data.code === 200) { fn(res.data) // } }).error() } } export default new Api() 複製程式碼
修改一下我們剛才 /blog/index.vue 的 Axios 請求:
- 先引入 api
import juejin from "@/api/juejin"; 複製程式碼
注掉之前離散的 axios ,使用從 api 中定義過的 XHR 請求資料。
getBlog() { // this.$http.get("/v1/getListByLastTime?src=web&pageNum=1").then(res => { //this.blogList = res.data.d; // }); juejin.getBlog("1", response => { this.blogList = response.d; }); } 複製程式碼

OK,很多同學不理解,本來簡單的事情為什麼搞複雜了?其實這樣不復雜,看起來還很清晰,倘若一個大專案上千個請求,不僅重複導致程式碼覆蓋率低,看起來還很亂,不利於團隊協作。本系列文章在帶領大家前後分離的基礎上,會為大家提供更多有利於團隊的開發方式,養成良好習慣。