[JavaScriptDungeon]LV.5 全台空氣指標儀表板

預覽圖

本層 BOSS 弱點

  • 【特定技術】必須使用 AJAX 技術串接資料 API,不可直接寫死資料在變數上
  • 【特定技術】上方切換城市 (高雄、台北) 後,下方會切換該城市的各地區
  • 【解決問題】糟糕,BOSS 使用屏蔽魔法將 API 出處移除了,身為勇者的你必須查出 API 的下落,才能順利擊敗此 BOSS。

使用技術

  • Vue.js
  • SCSS

心得

不知不覺小菜雞也挑戰到第五層了,這一層樓的版面看起來也是蠻輕鬆就能搞定,所以為了給自己一點點挑戰性,我稍微添加了一點互動回饋:

  • 使用 CSS 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」即可找到這個服務囉。

接著可以在程式碼內貼上這一段程式碼:

1
2
3
4
5
6
7
8
9
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 的功能了。

用法相當簡單,像是這樣即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 結合取得資料,所以決定自己動手做看看,用土法煉鋼的方式(?

首先因為我完全不懂,所以直接上網 google 了一下:

所以大概對於 Promise 有一點點的概念,總之就大概長得像這樣?

1
2
3
4
5
6
7
8
9
10
11
12
13
let promise = new Promise((resolve, reject) => {  
if (...) {
resolve();
} else {
reject();
}
});
promise.then((res) => {

});
promise.catch((error) => {

});

大概就是理解成,建立一個 Promise 然後可以用兩個函式 resolve 、 reject 分別代表兌現或是失敗。

  • 如果使用 resolve() ,接著程式會運行 .then 的部分
  • 如果使用 reject() ,接著程式會運行 .catch 的部分

然後搭配 XMLHttpRequest() 應該就沒問題了

並將程式碼修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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() 起手式是這樣,會回傳一個新陣列:

1
2
3
4
5
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 」的索引為 0
  • array— 這個陣列的內容

比方說想從陣列找出 apple ,可以在 return 後補上條件:

1
2
3
4
5
let arr = ['apple', 'banana', 'lemon', 'apple', 'watermelon', 'grape'];  
let result = arr.filter((item, index, array) =>{
return item === 'apple'
});
console.log(result); // ["apple", "apple"]

如果想找出陣列中的不重複值,則條件就複雜多了:

1
2
3
4
5
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 陣列的第一個元素是 applearray.indexOf('apple') 的結果為 0,所以實際上會像這個樣子 0 === index ,然而目前是陣列中的第一個元素,索引是 0 ,因此結果是 true ,將 apple 加入新陣列中。
  • 第二、第三個元素也同第一個元素,以此類推。
  • 當跑到第四個元素時,array.indexOf('apple') 的結果為 0,而當前的索引是 3 ,因此結果是 false ,不加入。
  • 第五個元素時,array.indexOf('watermelon') 的結果為 4,而當前的索引是 4,因此結果是 true ,將 watermelon 加入新陣列中。
  • 當所有元素都執行完時,回傳新陣列。

所以才能找出陣列中不重複的值,透過這樣的方式,要找出陣列中重複的值也很容易,只要 === 改成 !== 就可以了。

作品

0%