0%

[Vue]Vue套件介紹:axios

前提情要

筆者在公司的專案開發上選擇vue作為前端開發framework,不外呼就是開發應用程式,當然免不了基本的CRUD功能,此時需要一個強而有力的呼叫API套件,之所以選擇axios,沒有爾外的原因,因為是vue作者推薦使用,這樣不需有任何的疑慮,用下去就對了,此篇就以axios來探討其用法介紹及經驗分享。

使用工具及環境參考[Vue]Vue基本語法

內容

使用方式

我們要在專案上使用第一部就是使用npm來安裝它

1
npm install axios

若要在component上套用axios則,需先import它

1
import axios from 'axios';

接下來我們先來看一下簡單的get指令

1
2
3
4
5
6
7
8
// Make a request for a user with a given ID
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

建立axios實體

1
2
3
4
5
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});

視情況會使用到自己宣告出來的axios實體,筆者遇到的情形是

  1. 呼叫不同的API站台時
  2. 需要有獨立的config設定時

alias別名(簡寫)

以上使用到axios所提供的簡寫功能get,axios提供我們會常用到http action的簡寫宣告

  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.options(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

Config項目介紹

接下來介紹有哪些config可以設定,先來看一下一般的宣告方式

1
2
3
4
5
6
7
8
9
// Send a POST request
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});

完整的config項目列表,其中url為必填之外,其他為選填項目,筆者盡量以自己的理解的方式寫上註解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{
// 欲呼叫的目標API的URL
url: '/user',

// http action(對應到api定義的http action)
// 若沒宣告此屬性則,預設為get
method: 'get', // default

// 欲呼叫的目標API的BaseUrl
// 假設API網址有
// http://localhost:5000/api/user
// http://localhost:5000/api/user/5
// 對我們來說以上兩個API的BaseUrl為http://localhost:5000/api
baseURL: 'https://some-domain.com/api/',

// 傳送server端之前,可以透過此function撰寫統一處理邏輯
// 只支援於http action如put、post、patch
// 最後必須回傳處理過後的request封包,形式可為string/buffer/fomdata/stream等
transformRequest: [function (data, headers) {
// Do whatever you want to transform the data

return data;
}],

// 如同transformRequest,這個是針對response做處理
transformResponse: [function (data) {
// Do whatever you want to transform the data

return data;
}],

// 可自定義header參數
headers: {
'X-Requested-With': 'XMLHttpRequest'
},

// querystring參數
params: {
ID: 12345
},

// 可自定義parameter serialize的function
paramsSerializer: function (params) {
return Qs.stringify(params, {
arrayFormat: 'brackets'
})
},

// put/post/patch使用,request物件
data: {
firstName: 'Fred'
},

// 自定義request timeout時間,單位為豪秒ms
timeout: 1000
}

以上僅列出部份config項目,完整請參考axios github中的說明

全域設定

設定方式

axios很貼心的提供了全域設定的功能,等於說有些設定是共用的,可以透過defaults來設定,通常一定會設置一個baseURL,頁面上的呼叫api設定的url就只要設定相對url即可。

1
2
3
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

以上只是很簡單的舉了三個例子,第一個是設定baseURL,第二個是將登入成功後取得的token,設定於每次request中的header參數中,第三個是指定post時的content type為html encode過後的,以防腳本攻擊。這些只是簡單的例子,基本上defaults後面可以接上述提到的所有config項目。

設定優先順序探討

參考axiosgithub中的Config order of precedence

  1. request當中config
  2. defaults關鍵字設定之全域設定
  3. lib/defaults.js

Interceptor攔截器

使用方式

筆者特別想介紹這個部份,此功能非常重要,我們要做什麼事情,有了這個攔截器,根本就是事半功倍阿。簡單來說就是axios開放request前或response得到後事件,方便我們撰寫統一處理邏輯。

最簡單的應用就是我們在request的時候,無論是取得下拉式選單的data source,設定條件後按下「查詢」取得相對應的資料,都需要等待時間,現代都是求速成,連一秒都不願意等待,需要馬上有feedback,通常我們都會選擇使用顯示loading讓使用者知道我們很努力的在獲取資料。

這個時候這個攔截器的好處就浮現了,一般沒有攔截器的情況下,我們必須得在每一個request中必須處理顯示loading的邏輯,但這樣一來,重複的程式碼出現在不同地方,需要更改效果時根本就是惡夢阿。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});

移除攔截器

我們可以使用interceptor攔截器,當然也可以移除它

1
2
3
4
//使用const變數宣告request interceptor
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
//將上述interceptor使用eject的方式移除
axios.interceptors.request.eject(myInterceptor);

筆者當初看到這個說明會覺得不會使用到它,直到有些頁面中有autocomplete功能時,就會用上它了。我們專案的情境是我們會在main.js中宣告全域的interceptor,每次發出呼叫API的request前先將loading...畫面顯示,得到response後將其關閉。但問題來了,我們的autocomplete功能,也是會產生呼叫API的情形,會變成每打一個字會產生loading...效果,這是我們不希望的。

筆者在上述的問題解決方式採用上面提到的宣告一個新的axios實體方式,並且將該實體的interceptor設定中使用eject方式移除特定interceptor,當然在呼叫API時使用該axios實體

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 宣告於main.js中
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
Vue.prototype.$myInterceptor = myInterceptor;

// 以下為個別的Vue檔案中script區塊
// import axios
import axios from 'axios';
// 宣告axios實體變數於data區塊中
data(){
return{
myAxios: null
}
}
// 宣告axios實體初始化於created事件中
created: function(){
myAxios = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
myAxios.interceptors.request.eject(this.$myInterceptor);
}
// 該頁面中呼叫api時使用myAxios這個實體

多個請求Concurrency化

axios有提供多個request平行處理的功能all,然後可以使用spread將多個response當作參數接進來

1
2
3
4
5
6
7
8
9
10
11
12
axios.all([
axios.get('/users'),
axios.get('/posts', params:{
userId: userId
})
])
.then(
axios.spread((userResponse, postResponse)=> {
console.log(userResponse);
console.log(postResponse);
})
)

實例

我們來做一下實例吧,找一個公開的api jsonplaceholder 串接,並使用interceptor做進一步的處理。

安裝及設置前置作業

前置作業很多,但我會以這個專案為模版,其他文章的範例會使用這個專案繼續完成其他套件示範。

  1. 使用vue-cliinitial webpack模版專案
    1
    vue init webpack DemoApp
  2. 安裝bootstrap-vue
    接著使用visual studio code開啟專案,為了版面漂亮,我們來安裝一下bootstrap吧,為了使用現成的bootstrap component,我們就直接安裝bootstrap-vue
    1
    npm install bootstrap-vue --save
    接著我們在main.jsbootstrap-vue的import及使用宣告
    1
    2
    3
    4
    5
    6
    // import及使用
    import BootstrapVue from 'bootstrap-vue'
    Vue.use(BootstrapVue);
    // 載入相關css
    import 'bootstrap/dist/css/bootstrap.css'
    import 'bootstrap-vue/dist/bootstrap-vue.css'
  3. 安裝jquerypopper.js
    因為我們專案使用bootstrap 4版本,需要安裝jquerypopper.js
    1
    2
    npm install jquery --save
    npm install popper.js --save
  4. 安裝font-awesome
    1
    npm install font-awesome
    接著我們在main.js中載入相關css
    1
    import 'font-awesome/css/font-awesome.css'
  5. 安裝我們的主角axios
    1
    npm install axios
    以上面所介紹的,我們在main.js中加入defaults設定值,先加入baseURL
    1
    2
    3
    import axios from 'axios'

    axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com'

    撰寫程式

    這個範例會使用https://jsonplaceholder.typicode.com/posts做示範,先看一下回傳的object結構
    1
    2
    3
    4
    5
    6
    {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
    }
    我們這篇的範例就示範印出titlebody欄位。筆者就增加一點困難,我們就先列出title欄位列表,然後點下title後相對應的body內容才會以collapse效果彈出顯示,使用font-awesome在顯示隱藏效果下,切換顯示 符號。body欄位隱藏的狀態下會顯示 表示可以點開內容,若已顯示狀態下則切換顯示為 ,表示已打開內容。需求講完了,我們就來實際動手做吧。

    建立Component:AxiosDemo.vue

    Explorer視窗中,直接在components上按右鍵加入AxiosDemo.vue,我們先來宣告一下data部份,到時呼叫api後得到的response data使用這個data變數去接
    1
    2
    3
    4
    5
    data() {
    return {
    postInfos: []
    };
    }
    接下來我們就直接在created事件中呼叫API,取得資料,因我們已經於main.js中設置baseURL,geturl只要設置相對路徑即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    created: function() {
    axios.get("/posts").then(response => {
    let tempInfos = response.data;
    tempInfos.forEach(item => {
    item.show = false;
    this.postInfos.push(item);
    });
    });
    }
    以上程式碼稍微要講解一下,我們先用tempInfos接response data,之後再使用foreach倒出資料時,順邊指定show屬性,預設設為false,其控制body欄位資料顯示隱藏邏輯,設定好的資料push至我們的data變數postInfos

都設置完邏輯面的部份,我們來處理一下ui面吧,這裡會使用到bootstrap中的card結構,title欄位資料放於card-header中,body資料放於card-body中(預設隱藏)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="card"
v-for="post in postInfos"
:key="post.id">
<div class="card-header"
@click="post.show = !post.show">
<i class="fa fa-plus-square-o" v-show="!post.show"></i>
<i class="fa fa-minus-square-o" v-show="post.show"></i> {{post.title}}
</div>
<div class="card-body"
v-show="post.show">
<p class="card-text">
{{post.body}}
</p>
</div>
</div>

上述程式碼使用到vue相關技巧如下

  • vue directivev-forv-show
  • 事件監聽v-on@click
  • javascript表達式寫於@click,將show改為目前show值相反值
    • 例:true改為false、false則反之。
  • 一定要會的Mustache語法使用,將titlebody欄位值印出

完整的AxiosDemo.vue程式碼如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<div id="axiosdemo">
<div class="card"
v-for="post in postInfos"
:key="post.id">
<div class="card-header"
@click="post.show = !post.show">
<i class="fa fa-plus-square-o" v-show="!post.show"></i>
<i class="fa fa-minus-square-o" v-show="post.show"></i> {{post.title}}
</div>
<div class="card-body"
v-show="post.show">
<p class="card-text">
{{post.body}}
</p>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "AxiosDemo",
data() {
return {
postInfos: []
};
},
created: function() {
axios.get("/posts").then(response => {
let tempInfos = response.data;
tempInfos.forEach(item => {
item.show = false;
this.postInfos.push(item);
});
});
}
};
</script>

設定Route

我們找到router\index.js,擴充routes陣列

1
2
3
4
5
6
7
8
9
// 先import Component
import AxiosDemo from '@/components/AxiosDemo'

// 擴充routes陣列
{
path: 'axios',
name: 'AxiosDemo',
component: AxiosDemo
}

使用npm run dev執行網站,效果如下

套用axios攔截器及loading效果

上面有介紹到interceptor攔截器的功能,筆者就以上述範例繼續以擴充的方式完成

  1. 安裝loading效果實現套件vue-blockui
    1
    npm install vue-blockui --save
  2. 接著在main.js中引入
    1
    2
    3
    4
    // 將套件import進來
    import BlockUI from 'vue-blockui'
    // 套用於vue中
    Vue.use(BlockUI)
  3. AxiosDemo.vue頁面中加入vue-blockuiui區塊
    1
    2
    3
    4
    5
    6
    <!-- 使用data變數spinnerStatus來控制顯示與否 -->
    <!-- 使用data變數msg來動態設定顯示字串 -->
    <BlockUI :message="msg"
    v-show="spinnerStatus">
    <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
    </BlockUI>
  4. 上步驟宣告中有使用到data變數
    1
    2
    3
    4
    5
    6
    7
    data() {
    return {
    postInfos: [],
    msg: "Loading...",
    spinnerStatus: false
    };
    },
  5. created事件中加入相關程式碼
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    let $vue = this;
    axios.interceptors.request.use(
    function(config) {
    $vue.spinnerStatus = true;
    return config;
    },
    function(error) {
    $vue.spinnerStatus = false;
    return Promise.reject(error);
    }
    );
    // 使用setTimeout是為了demo效果,不然資料撈太快,會沒看到loading效果
    // 因此故意延遲3秒後才隱藏loading視窗
    axios.interceptors.response.use(
    function(response) {
    setTimeout(function() {
    $vue.spinnerStatus = false;
    }, 3000);
    return response;
    },
    function(error) {
    $vue.spinnerStatus = false;
    return Promise.reject(error);
    }
    );
  6. 完整的程式碼(加入axios.getaxios.interceptors)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    <template>
    <div id="axiosdemo">
    <div class="card"
    v-for="post in postInfos"
    :key="post.id">
    <div class="card-header"
    @click="post.show = !post.show">
    <i class="fa fa-plus-square-o"
    v-show="!post.show"></i>
    <i class="fa fa-minus-square-o"
    v-show="post.show"></i> {{post.title}}
    </div>
    <div class="card-body"
    v-show="post.show">
    <p class="card-text">
    {{post.body}}
    </p>
    </div>
    </div>
    <!-- blockui -->
    <BlockUI :message="msg"
    v-show="spinnerStatus">
    <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
    </BlockUI>
    </div>
    </template>
    <script>
    import axios from "axios";
    export default {
    name: "AxiosDemo",
    data() {
    return {
    postInfos: [],
    msg: "Loading...",
    spinnerStatus: false
    };
    },
    created: function() {
    let $vue = this;
    axios.interceptors.request.use(
    function(config) {
    $vue.spinnerStatus = true;
    return config;
    },
    function(error) {
    $vue.spinnerStatus = false;
    return Promise.reject(error);
    }
    );

    // Add a response interceptor
    axios.interceptors.response.use(
    function(response) {
    setTimeout(function() {
    $vue.spinnerStatus = false;
    }, 3000);
    return response;
    },
    function(error) {
    $vue.spinnerStatus = false;
    return Promise.reject(error);
    }
    );
    axios.get("/posts").then(response => {
    let tempInfos = response.data;
    tempInfos.forEach(item => {
    item.show = false;
    this.postInfos.push(item);
    });
    });
    }
    };
    </script>
    最終完成效果如下

結論

我們建構應用程式最基本的功能就是CRUD,axios套件是一定要投資的,筆者建議一定要花時間研究及練習,老話一句,一定要記得axios提供哪些功能,這樣需要用到時,很快就能查出相對應的解法,介紹就到這邊,我們下篇再見。

範例檔案參考https://github.com/EugeneSu0515/VueDemoApp