記第一個Vue專案臺前幕後的經歷

hantmac發表於2019-01-29

背景:

部門有個需要前端展示的頁面,0前端開發經驗,初次接觸Vue,實現從後端到前端,從入門,開發、打包到部署,完整的歷程。

官方文件入門

ES6基礎知識瞭解,看了阮一峰的ES6入門

然後粗略擼了一遍Vue官方文件,動手用webpack搭建了一個簡單的demo。

看了Echarts的官方demo,瞭解了幾種資料圖表的資料結構。因為我要做的專案就是要將後端介面的資料拿到,然後圖形化的形式展示出來。

下面是整個專案過程中的關鍵點以及一些坑、

後端介面開發

後端採用Golang web框架beego實現RESTFul介面,參考了官方文件,然後自己寫了一個小demo。比較容易上手,一週就完成後端介面。

Vue對接後端,進行axios二次開發

在構建應用時需要訪問一個 API 並展示其資料,調研Vue的多種方式後選擇了官方推薦的axiox。

從ajax到fetch、axios

前端是個發展迅速的領域,前端請求自然也發展迅速,從原生的XHR到jquery ajax,再到現在的axios和fetch。

jquery ajax

$.ajax({
    type: 'POST',
    url: url,
    data: data,
    dataType: dataType,
    success: function() {},
    error: function() {}
})
複製程式碼

它是對原生XHR的封裝,還支援JSONP,非常方便;真的是用過的都說好。但是隨著react,vue等前端框架的興起,jquery早已不復當年之勇。很多情況下我們只需要使用ajax,但是卻需要引入整個jquery,這非常的不合理,於是便有了fetch的解決方案。

fetch

fetch號稱是ajax的替代品,它的API是基於Promise設計的,舊版本的瀏覽器不支援Promise,需要使用polyfill es6-promise

舉個例子:

// 原生XHR
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(xhr.responseText)   // 從伺服器獲取資料
    }
}
xhr.send()
// fetch
fetch(url)
    .then(response => {
        if (response.ok) {
            response.json()
        }
    })
    .then(data => console.log(data))
    .catch(err => console.log(err))

複製程式碼

看起來好像是方便點,then鏈就像之前熟悉的callback。

在MDN上,講到它跟jquery ajax的區別,這也是fetch很奇怪的地方:

當接收到一個代表錯誤的 HTTP 狀態碼時,從 fetch()返回的 Promise 不會被標記為 reject, 即使該 HTTP 響應的狀態碼是 404 或 500。相反,它會將 Promise 狀態標記為 resolve (但是會將 resolve 的返回值的 ok 屬性設定為 false ), 僅當網路故障時或請求被阻止時,才會標記為 reject。 預設情況下, fetch 不會從服務端傳送或接收任何 cookies, 如果站點依賴於使用者 session,則會導致未經認證的請求(要傳送 cookies,必須設定 credentials 選項).

突然感覺這還不如jquery ajax好用呢?別急,再搭配上async/await將會讓我們的非同步程式碼更加優雅:

async function test() {
    let response = await fetch(url);
    let data = await response.json();
    console.log(data)
}

複製程式碼

看起來是不是像同步程式碼一樣?簡直完美!好吧,其實並不完美,async/await是ES7的API,目前還在試驗階段,還需要我們使用babel進行轉譯成ES5程式碼。

還要提一下的是,fetch是比較底層的API,很多情況下都需要我們再次封裝。 比如:

// jquery ajax
$.post(url, {name: 'test'})
// fetch
fetch(url, {
    method: 'POST',
    body: Object.keys({name: 'test'}).map((key) => {
        return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
    }).join('&')
})
複製程式碼

由於fetch是比較底層的API,所以需要我們手動將引數拼接成'name=test'的格式,而jquery ajax已經封裝好了。所以fetch並不是開箱即用的。

另外,fetch還不支援超時控制。

哎呀,感覺fetch好垃圾啊,,還需要繼續成長。。

axios

axios是尤雨溪大神推薦使用的,它也是對原生XHR的封裝。它有以下幾大特性:

  • 可以在node.js中使用
  • 提供了併發請求的介面
  • 支援Promise API

簡單使用

axios({
    method: 'GET',
    url: url,
})
.then(res => {console.log(res)})
.catch(err => {console.log(err)})
複製程式碼

併發請求,官方的併發例子:

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // Both requests are now complete
  }));
複製程式碼

axios體積比較小,也沒有上面fetch的各種問題,我認為是當前最好的請求方式

詳情參考官方文件

二次封裝axios

為了方便開發,將axios的方法結合後端介面,進行封裝,使用起來會比較方便。

首先建立一個request.js,內容如下:

import axios from 'axios';
import Qs from 'qs';


function checkStatus(err) {
    let msg = "", level = "error";
    switch (err.response.status) {
      case 401:
        msg = "您還沒有登陸";
        break;
      case 403:
        msg = "您沒有該項許可權";
        break;
      case 404:
        msg = "資源不存在";
        break;
      case 500:
        msg = "伺服器發生了點意外";
        break;
    }
    try {
      msg = res.data.msg;
    } catch (err) {
    } finally {
      if (msg !== "" && msg !== undefined && msg !== null) {
        store.dispatch('showSnackBar', {text: msg, level: level});
      }
    }
    return err.response;
  }
  
  function checkCode(res) {
    if ((res.status >= 200 && res.status < 400) && (res.data.status >= 200 && res.data.status < 400)) {
      let msg = "", level = "success";
      switch (res.data.status) {
        case 201:
          msg = "建立成功";
          break;
        case 204:
          msg = "刪除成功";
          break;
      }
      try {
        msg = res.data.success;
      } catch (err) {
      } finally {
        
      }
      return res;
    }

    return res;
  
  }

//這裡封裝axios的get,post,put,delete等方法
export default {
    get(url, params) {
      return axios.get(
        url,
        params,
      ).then(checkCode).catch((error)=>{console.log(error)});
    },
    post(url, data) {
      return axios.post(
        url,
        Qs.stringify(data),
      ).then(checkCode).catch(checkStatus);
    },
    put(url, data) {
      return axios.put(
        url,
        Qs.stringify(data),
      ).then(checkCode).catch(checkStatus);
    },
    delete(url, data) {
      return axios.delete(
        url,
        {data: Qs.stringify(data)},
      ).then(checkCode).catch(checkStatus);
    },
    patch(url, data) {
      return axios.patch(
        url,
        data,
      ).then(checkCode).catch(checkStatus);
    },
  };
複製程式碼

建立一個api.js,存放後端的介面:

//匯入上面的request模組
import request from './request';

//宣告後端介面
export const urlUserPrefix = '/v1/users';
export const urlProductPrefix = '/v1/products';

//使用前面封裝好的方法,呼叫後端介面
export const getUserslInfoLast = data => request.get(`${urlUserPrefix}`, data);
export const getProductsInfo = data => request.get(`${urlProductPrefix}`, data);

複製程式碼

在.vue檔案中使用定義的方法,獲取後端介面的資料:

export default  {

    components: {
    chart: ECharts,
   
  },
  store,
    name: 'ResourceTypeLine',
    data: () =>({
       seconds: -1,
      
        //define dataset
   apiResponse:{},
   

    initOptions: {
        renderer: options.renderer || 'canvas'
      },
      
    mounted:function() {
   
this.fTimeArray = this.getFormatTime()

//呼叫method裡面的方法
this.getUserInfo()
   
    },
    methods: {
//非同步方式呼叫後端介面
       async getUserInfo() {
        const resThis = await urlUserPrefix({
          params: {
              //get的引數在這裡新增
            beginTime: this.fTimeArray[0],
            endTime: this.fTimeArray[1],
          }
         
        });
        this.apiResponseThisMonth = resThis.data
        try {
        } catch (err) {
          console.log(err);
        } 
      },
複製程式碼

開發環境配置跨域

為了更方便地與後臺聯調,需要在用vue腳手架建立地專案中,在config目錄的index.js檔案設定proxytable來實現跨域請求,具體程式碼如下:

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '.',
    productionSourceMap: false,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    // hosts:"0.0.0.0",
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    //配置跨域請求,注意配置完之後需要重啟編譯該專案
    proxyTable: {
      //請求名字變數可以自己定義
      '/api': {
        target: 'http://test.com', // 請求的介面域名或IP地址,開頭是http或https
        // secure: false,  // 如果是https介面,需要配置這個引數
        changeOrigin: true,// 是否跨域,如果介面跨域,需要進行這個引數配置
        pathRewrite: {
          '^/api':""//表示需要rewrite重寫路徑  
        }
      }
    },
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}
複製程式碼

vue 專案打包部署,通過nginx 解決跨域問題

由於開發環境中配置的跨域在將專案打包為靜態檔案時是沒有用的 ,就想到了用 nginx 通過反向代理的方式解決這個問題,但是其中有一個巨大的坑,後面會講到。

前提條件

liunx 下 nginx 安裝配置(將不做多的闡述,請自行百度)

配置nginx
  • 在配置檔案中新增一個server
# 新增的服務 
	# 新增的服務
	server {
		listen       8086; # 監聽的埠

		location / {
			root /var/www;  # vue 打包後靜態檔案存放的地址
			index index.html; # 預設主頁地址
		}
	
		location /v1 {
			proxy_pass http://47.106.184.89:9010/v1; # 代理介面地址
		}
	
		location /testApi {
			proxy_pass http://40.106.197.89:9086/testApi; # 代理介面地址
		}
		error_page   500 502 503 504  /50x.html;
		location = /50x.html {
			root   html;
		}

	}
複製程式碼
  • 解釋說明

/var/www是我當前將vue 檔案打包後存放在 liunx下的路徑 ,

當我們啟動 nginx 後 就可以通過http://ip地址:8086/訪問到vue 打包的靜態檔案。

2.location /v1 指攔截以v1開頭的請求,http請求格式為http://ip地址:8086/v1/***,這裡有一個坑!一定要按照上面的配置檔案**:proxy_pass http://47.106.184.89:9010/v1;如果你像我一開始寫的:proxy_pass http://47.106.184.89:9010/;,你永遠也匹配不到對應的介面!意思是你的介面地址以v1開頭,location的匹配也要以v1`開頭。

proxy_pass http://47.106.197.89:9093/v1;` 當攔截到需要處理的請求時,將攔截請求代理到介面地址。

webpack打包

下面是config/index.js配置檔案

// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '.',
    productionSourceMap: false,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    // hosts:"0.0.0.0",
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    //配置跨域請求,注意配置完之後需要重啟編譯該專案
    proxyTable: {
      //請求名字變數可以自己定義
      '/api': {
        target: 'http://billing.hybrid.cloud.ctripcorp.com', // 請求的介面域名或IP地址,開頭是http或https
        // secure: false,  // 如果是https介面,需要配置這個引數
        changeOrigin: true,// 是否跨域,如果介面跨域,需要進行這個引數配置
        pathRewrite: {
          '^/api':""//表示需要rewrite重寫路徑  
        }
      }
    },
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}

複製程式碼

Dockerfile

打包Docker映象

FROM Nginx:base

MAINTAINER hantmac <hantmac@outlook.com>

WORKDIR /opt/workDir
RUN mkdir /var/log/workDir

COPY dist /var/www

ADD  nginx/default.conf /etc/nginx/conf.d/default.conf

ENTRYPOINT nginx -g "daemon off;"
複製程式碼

後記

這是首次接觸前端的第一個專案,期間經歷了從後端介面開發,前端框架選型(一度想要用react,後來還是放棄),熟悉Vue,到元件開發,webpack構建,Nginx部署,Docker釋出的完整過程。雖然頁面比較簡單,但是期間的採坑無數。Vue確實是對小白比較友好,廣大後端er可以玩耍起來了!


相關文章