[使用技術]
- Vue.js
- SCSS
[心得]
不知不覺小菜雞也挑戰到第五層了,這一層樓的版面看起來也是蠻輕鬆就能搞定,所以為了給自己一點點挑戰性,我稍微添加了一點互動回饋:
- 使用 CSS3 的 animation 製作 Loading 畫面
- 點擊時補上內凹的陰影,使其更加真實
接著就是寫程式的部分了,不得不說做這種跟資料相關的版面,使用 Vue 真的是輕鬆很多,只要專注在資料的處理上就好了,所以當我看到這次的題目是 AQI 儀表板,想都沒想就決定要用 Vue 寫了。
所以我說那個 API 在哪?
身為一個前端菜雞,菜歸菜但基本的資料蒐集能力還是要有的,我下的關鍵字是「 行政院環境保護署 API」,果不其然第一筆搜尋結果就是答案。
一進網站就馬上發現目標了,空氣品質指標 AQI ,接著只要切到資料檢視頁籤,在選擇 JSON 格式就可以囉。
CORS 怎麼處理?
以下引用自 MDN 的說明:跨來源資源共用(Cross-Origin Resource Sharing (CORS))是一種使用額外 HTTP 標頭令目前瀏覽網站的使用者代理取得存取其他來源(網域)伺服器特定資源權限的機制。當使用者代理請求一個不是目前文件來源 — — 例如來自於不同網域(domain)、通訊協定(protocol)或通訊埠(port)的資源時,會建立一個跨來源 HTTP 請求(cross-origin HTTP request)。
白話來說,就是這支空氣品質指標的 API 沒有打開 CORS ,所以如果我們把做好的網頁成品發佈到 GitHub 、 Codepen 的話是不能撈取資料的。
我之前的作法都是使用別人建立好的服務,像是這個:
用法相當容易,只需要在後方加入要使用的 API 即可。
但是這一次,我在討論區上看到其他大神們分享這個方法,讓我躍躍欲試。
使用 Google Apps Script 做中繼點跨網域遠端取得 API 資料
使用這個服務必須要申請一個 Google 帳號,接著我們來到雲端硬碟的畫面。
左上角有個「新增」按鈕點進去即可看到這個畫面,因為之前我已經有操作過了,如果是第一次使用要點選「連結更多應用程式」,接著於搜尋欄輸入「script」即可找到這個服務囉。
接著可以在程式碼內貼上這一段程式碼:
function doGet(e){
var param = e.parameter;
var url = param.url;
var response = UrlFetchApp.fetch(decodeURIComponent(url),{
headers: { "Content-type" : "application/json" }
});
var data = JSON.parse(response.getContentText());
return ContentService.createTextOutput(JSON.stringify(data)).setMimeType(ContentService.MimeType.JSON);
}
接著按下發布 > 部署為網路應用程式
使用方式:部署的網址?參數名稱= API 網址
這樣子只要一次工,之後練習的作品全部都可以透過這個服務解決掉 CORS 的問題唷~如果是工作上遇到的 CORS ,可能就不適合這個方法囉。
本段圖片、程式碼引用自此,感謝大大的分享。
取得 API 資料的方式
這方式也是有蠻多種的,如果想方便一點可以使用 axios 這個非常強大的套件,而且討論區中也非常多人使用,而且它本身也有 Promise 的功能了。
用法相當簡單,像是這樣即可:
axios({
method:'get',
url:apiUrl,
})
.then(function (response) {
vm.allData = response.data;
vm.updateTime = vm.getTime();
vm.Detail = vm.initDetail();
vm.isLoading = false;
})
.catch(function (error) {
vm.updateTime = vm.getTime();
vm.isLoading = false;
console.log('取得資料失敗:' + error);
});
更詳細可以參考 axios 的官方說明。
雖然說這樣就可以了,不過因為我不懂 Promise 是什麼,該如何與 Ajax 結合取得資料,所以決定自己動手做看看,用土法煉鋼的方式(?
首先因為我完全不懂,所以直接上網 goo 了一下:
所以大概對於 Promise 有一點點的概念,總之就大概長得像這樣?
let promise = new Promise((resolve, reject) => {
if (...) {
resolve();
} else {
reject();
}
});
promise.then((res) => {
});
promise.catch((error) => {});
大概就是理解成,建立一個 Promise 然後可以用兩個函式 resolve 、 reject 分別代表兌現或是失敗。
- 如果使用 resolve() ,接著程式會運行 .then 的部分
- 如果使用 reject() ,接著程式會運行 .catch 的部分
然後搭配 XMLHttpRequest() 應該就沒問題了 (吧?
並將程式碼修改如下:
let promise = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', apiUrl, true);
xhr.send(null);
xhr.onload = () =>{
if (xhr.status >= 200 && xhr.status < 400) {
resolve(xhr.response);
} else {
reject("取得資料失敗: " + xhr.status);
}
}
});
promise.then((res) => {
vm.allData = JSON.parse(res);
});
promise.catch((error) => {
console.log(error);
});
這樣就完成了呢,不過這邊有個小小的問題,就是用 XMLHttpRequest() 取到的結果會是字串,要額外透過 JSON.parse() 轉成 JSON 才可以使用。
所以結論就是 axios 超棒 (诶?
使用 filter() 方法取出不重複的值
處理好 API 的問題後,再來就要實作內容了,這個部份很基本,不過我卻每次都會忘記該怎麼處理,所以決定這一次把它寫下來。
而取出陣列中不重複的值做法有很多種,我習慣用 filter() 就是了。
基本的 filter() 起手式是這樣,會回傳一個新陣列:
let arr = ['apple', 'banana', 'lemon', 'apple', 'watermelon', 'grape'];
let result = arr.filter((item, index, array) =>{
console.log(item, index, array);
return item;
});
而這些參數分別代表為:
item
— 當前是arr
陣列中的哪一個值,如「apple」index
— 這個值在arr
陣列中的索引,如「apple」的索引為 0array
— 這個陣列的內容
比方說想從陣列找出 apple ,可以在 return
後補上條件:
let arr = ['apple', 'banana', 'lemon', 'apple', 'watermelon', 'grape'];
let result = arr.filter((item, index, array) =>{
return item === 'apple'
});
console.log(result); // ["apple", "apple"]
如果想找出陣列中的不重複值,則條件就複雜多了:
let arr = ['apple', 'banana', 'lemon', 'apple', 'watermelon', 'grape'];
let result = arr.filter((item, index, array) =>{
return array.indexOf(item) === index;
});
console.log(result);
使用了一個方法 indexOf() ,這會回傳從陣列中第一個被找到的目標索引,若不存在於陣列中則回傳 -1。
也就是說這段程式碼實際上是這麼跑的:
- arr 陣列的第一個元素是 apple ,
array.indexOf('apple')
的結果為 0,所以實際上會像這個樣子0 === index
,然而目前是陣列中的第一個元素,索引是 0 ,因此結果是 true ,將 apple 加入新陣列中。 - 第二、第三個元素也同第一個元素,以此類推。
- 當跑到第四個元素時,
array.indexOf('apple')
的結果為 0,而當前的索引是 3 ,因此結果是 false,不加入。 - 第五個元素時,
array.indexOf('watermelon')
的結果為 4,而當前的索引是 4,因此結果是 true ,將 watermelon 加入新陣列中。 - 當所有元素都執行完時,回傳新陣列。
所以才能找出陣列中不重複的值,透過這樣的方式,要找出陣列中重複的值也很容易,只要 === 改成 !== 就可以了。