--- title: 'VueJS 2.0 教學筆記: 生命週期與AXIOS API' disqus: hackmd --- VueJS 2.0 教學筆記: 生命週期與AXIOS API === 綱要 [TOC] 一. Vue Life Circle 生命週期 --- ![](https://i.imgur.com/MLYrjeX.jpg) 當瀏覽器開始new Vue()階段時,會依序經過以下步驟,且每個步驟都可以根據需要的階段來掛載methods: ==**beforeCreate()**== 觀察`data()`結構與初始化 events ==**created()**== 是否有"el"的 option 被調用,假如有則判斷是否有"template"元素出現,沒有的話則 view model 呼叫 mount 掛載 el 。如果有 `template`,將 `template` 中的 html 模板編譯到 `render()` 中,若沒有 `template` 出現在環境則編譯 el 上一層的 HTML 作為 `template` 模板。 **注意:`created()`階段時頁面 DOM 還不會生成** ==**beforeMount()**== Vue掛載初始化並處理好的組件設定與事件之前,會建構一個`vm.$el`屬性物件替換el。 ==**mounted()**== 掛載階段,如果data有被更動,則進入`beforeUpdate()`。 ==**beforeUpdate()**== 將VDOM重新render並更新。 ==**updated()**== 更新data後的狀態。 ==**beforeDestroy()**== 當`mounted()`後如果有呼叫`vm.$destroy()`的行為,則進入`beforeDestroy()` 此時拆卸`watcher`(比如compted的getter或watch)和子組件、以及事件偵聽後進入`destroyed()`消滅。 二、router 路由新增頁面練習 --- [GitHub範例](https://github.com/fortes1219/vue_0803/blob/0803/src/router.js) :::info 注意,假如你也有跟我一樣要把 Home 設定起始畫面就是 Dashboard 才跟著做,如果要改成獨立分頁,寫在跟 home 平行同層物件下即可。 ::: ```javascript= { path: 'newpage' //路徑。也就是網址打什麼後綴字會出現 name: 'newPage', //名稱,router切換頁面$router.push('newPage')會使用這個名字 component: () => import './views/newPage.vue' // 將新增頁面的vue檔案拉到這 } ``` 三、Vue 使用 axios 的封裝方式 --- :::info 除了不需要 Call API 取得 Data List 或 Select Options 而會將設定寫入 data() 外,儘早讓各位同學習慣從 JSON Server 模擬環境 Get 資料,提前安排 Axios + vue-axios 在這個禮拜講解實作一次。這個例子只是方便大家從JSON Server中取資料來操作一遍,實際環境專案使用的封裝方式最後會提到。 ::: 先安裝以下套件: `npm install axios` `npm install vue-axios` `npm install json-server` 然後在專案的根目錄下新增 `db.json`,並且新增一些內容。[GitHub範例](https://github.com/fortes1219/vue_0803/blob/0803/db.json) 接下來開啟終端程式輸入 `json-server --watch db.json` 就會看到這個畫面 ``` \{^_^}/ hi! Loading db.json Done Resources http://localhost:3000/posts http://localhost:3000/comments http://localhost:3000/profile http://localhost:3000/tableData ##剛剛在db.json新增的資料 Home http://localhost:3000 ``` ==1. main.js當中引入並且設定global prototype== [GitHub範例](https://github.com/fortes1219/vue_0803/blob/0803/src/main.js) ```javascript= /* main.js */ import axios from 'axios' import api from '@/service/api' // 第二步驟會用到 import VueAxios from 'vue-axios' // 教學專案採用element UI組件來切版和建置表單,所以先裝好 import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' // 套用並設定prototype Vue.use(ElementUI) Vue.use(VueAxios, axios) Vue.config.productionTip = false Vue.prototype.$api = api // 定義api這個常數給AXIOS存取json-server或實際api環境用 ``` ==2. 在src裡面創建一個叫做「service」的目錄,此目錄下再創建一個「api.js」== [GitHub範例](https://github.com/fortes1219/vue_0803/blob/0803/src/service/api.js) ```javascript= /* api.js */ import axios from 'axios' axios.defaults.baseURL = 'http://localhost:3000/' const api = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, get: (url, params) => { return new Promise((resolve, reject) => { axios.get(url, { params: params }) .then((response) => { resolve(response.data) }) .catch((error) => { reject(error) }) }) }, post: (url, params) => { return new Promise((resolve, reject) => { axios.post(url, params) .then((response) => { resolve(response.data) }) .catch((error) => { reject(error) }) }) }, put: (url, params) => { return new Promise((resolve, reject) => { axios.put(url, params) .then((response) => { resolve(response.data) }) .catch((error) => { reject(error) }) }) }, delete: (url, params) => { return new Promise((resolve, reject) => { axios.delete(url) .then((response) => { resolve(response.data) }) .catch((error) => { reject(error) }) }) } } export default api ``` ==3. 封裝好axios後回到頁面檔案== [GitHub範例](https://github.com/fortes1219/vue_0803/blob/0803/src/views/ApiTemp.vue) ```htmlmixed= <template> <!--ApiTemp.vue--> <div> API測試 </div> </template> ``` ```javascript= <script> export default { data() { return{ tableData: [] //宣告個空陣列 } }, methods: { async packageGetData() { const url = 'tableData' // json-server API 位置 let res = await this.$api.get(url) this.tableData = [...res] // 透過ES6語法將res的內容直接繼承到tableData console.log(res) }, }, created() { this.packageGetData() // 在created 階段把API資料叫進來 } } </script> ``` 接著在開發人員工具畫面的Console中就可以看到我們從 `tableData` Get 回來的資料了。 真實專案上接 API 是什麼樣的狀況? --- 實際專案使用Axios多以POST為主,包含取得資料(GET)和修改(UPDATE)、刪除(DELETE)等行為都是給一個包裝好指定參數的物件,送到後端驗證之後回傳新的結果,真正在有後端同事配合的狀況下,會需要一些環境變數,也要把串接的CODE改寫,包含封裝和攔截器設定等。 **QS套件** 通常主要用來處理原生form物件POST行為當中 application/x-www-form-urlencoded 編碼上的問題,先轉字串後再使用這個套件做`Qs.stringify`,但說句實話,「Server Side 可以完全無視這件事」,僅在這稍稍提一下它的存在是幹嘛的。 `npm install qs` **.env.development** 這邊視部署情況去更改不同檔名的參數,比如QAT就是staging,PROD就是production ```shell= ENV = 'development' VUE_APP_BASE_API = 'http://my-api-server.com' VUE_APP_VERSION = '0.0.0' ``` **api.js** ```javascript= import axios from 'axios' import Cookies from 'js-cookie' // QS用不用其實都可以,前面說過這個機制Server Side可以無視 import Qs from 'qs' export function request(config) { const service = axios.created({ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 60000, transformRequest: [(data) => { let ret = '' const tempData = getJwtData(data) for (const it in tempData) { ret += encodeURIComponent(it) + '=' + encodeURIComponent(tempData[it]) + '&'; } return ret }] }) const getJwtData = (data) => { // 如果jwt的key中含有空字串或者undefined,刪除該條jwt for (const key in data) { if (data.hasOwnProperty(key)) { const val = data[key] if (val === '' || val === undefined){ delete data[key] } } } const userInfo = Cookies.get('userInfo') let token = '' if (userInfo) { token = JSON.parse(userInfo).token } const jwt = token + '.' + encodeURIComponent(btoa(encodeURIComponent(JSON.stringify(data)))) return {data:jwt} } // ---> // 設置 request 攔截器 axios.interceptors.request.use((config) => { const token = JSON.parse(Cookies.get('userInfo').token) // token本身是會過期的,需要返回狀態查詢是不是過期 token && (config.headers.Authorization = token) return config }) // 設置 response 攔截器 axios.interceptors.response.use( (response) => { // 如果回應200表示正常連線,可以返回資料結果,反之reject if (response.status === 200) { return Promise.resolve(response); } else { return Promise.reject(response); } }, // 根據不同的回應碼來訂製不同的錯誤訊息 (error) => { if (error && error.response) { switch (error.response.status) { case 400: error.message = 'Request Error!' break case 401: error.message = 'No permission, need login.' break case 403: error.message = 'Access denied!' break case 404: // 自動帶入 request 地址的寫法 error.message = `Address not exist: ${error.response.config.url}` break case 408: error.message = 'Request timeout!' break case 500: error.message = 'Server inside error!' break case 501: error.message = 'Service not allowed!' break case 502: error.message = 'Bad gateway!' break case 503: error.message = 'No service!' break case 504: error.message = 'Gateway timeout!' break case 505: error.message = 'http version not supported!' break default: break } } Message({ message: error.message, type: 'error', duration: 5 * 1000 }) console.log('error',error) return Promise.reject(error) } ) return service(config) } ``` **api_path.js** ```javascript= import request from '@/api' // api.js export const GetDataList = (data) => request({ url: '/post/tableData', method: 'post', data, }) ``` **component.vue** ```htmlmixed= <template> <div id="page"> <el-table :data="tableData" > ... ... </el-table> </div> </template> ``` ```javascript= <script> import { GetDataList } from '@/api_path' export default { data() { return { tableData: [], currentPage: 1, pageLimit: 20 } }, computed: { getLanguage() { return this.$store.lang } }, methods: { async fetchDataList() { const jwt = { "language": this.getLanguage(), "page": this.currentPage, "page_limit": this.pageLimit } console.log('post jwt: ', jwt) let res = await GetDataList(jwt) if (res.result == '1') { this.tableData = res.data console.log('get result: ', res.data) } else { alert(res.data.errMessage) } } }, created() { // created 階段就開始發出request this.fetchDataList() } } </script> ``` ###### tags: `VueJS` `Axios`