超簡單學習 Vue.js 系列 | Vue tsmc 常用英文單字測驗練習

2022-05-07

Vue.js 簡單的示範專案,使用 Vue CLI 實作 tsmc 常用英文單字測驗練習,結合 ES6 語法特性活用 Array 與 Object,嘗試使用 Html5 Audio Element 用按鈕音效讓使用體驗更佳,並且重構原本的 Vue App。

logo

TSMC 常見英文單字測驗練習 Vue Multiway Buttons

Demo

Html 5 Audio

import successSound from '@/assets/success.mp3';

```vue
export default {
  data() {
    return {
      audioSuccess: new Audio('/success.mp3'),
      audioFail: new Audio('/fail.mp3'),
    };
  },
}
async playAudio(bool) {
  if (bool === true) {
    this.audioSuccess.play();
  } else {
    this.audioFail.play();
  }
},
this.audioSuccess?.paused

在本次的開發,嘗試了使用 Audio 播放音效的方式,在答對或者答錯題目時,會有不同的音效,在 PC 瀏覽器環境測試一切符合預期;但在 Mobile 上,答題過程偶爾會因為音效的播放,整個畫面凍結住,約 10 秒以後才會恢復,讓整個使用者體驗非常不好。因此雖然試驗了播放音效,但在實際的成果仍不加上,並且留待未來突破這個問題。

ES6 Object & Arrays

在設計隨機問題、隨機答案選項的時候,一開始用了好幾個變數來分別代表,但最後卻發現邏輯之間會有問題,當題目數小於需要被抽出的答案數時,會發生 Bug。於是開始思考如何重構原本的邏輯設計。

但在實作的時候,卡關好幾個地方,後來讓頭腦交替在專注與發散模式後,終於釐清是對於 JS Object & Arrays 的使用不夠熟悉 (因為 Python 的資料結構物件用得太過癮,對於其他的語言都會卡卡的 😗)

於是特別開 Node.js 來自問自答,深入認識 JS 的 Object 與 Arrays,以下是過程中的收穫:

let data = require('./quesbank.json')

而 Json 的格式預設為花括號物件,如果是清單類型,需要先指定一個 Properties 再搭配 Array,或者用巢狀花括號物件 (Nested Object)的方式表示。

Nested Object

{
  "Alice": { "age": 21},
  "Bob": { "age": 23},
  "Charlie": { "age": 22}
}

Properties with Array

{
  "users": [
    "Alice": { "age": 21},
    "Bob": { "age": 23},
    "Charlie": { "age": 22}
  ]
}

在製作資料的時候,筆者喜歡用 Nested Object 的方式,但在 JS 的功能實現上,會需要 Array Method 的支援,所以還是會將 Nested Object 進行轉型為 Array:

Object.values(data)

轉型後就可以使用多元且方便的 Array Method 進行處理,包括 FilterMapEverySome 以及 Reduce

相關語法的介紹可以參考 ES20XX Modern JS 新語言特性筆記 | Array

重新熟悉 JS 的 Object 以及 Array 後,原本要解決的抽取隨機問題、隨機答案選項的困難,最後化約到在原本的 Json 資料物件上用一個狀態屬性來加工,問題被分成三個狀態,undefined、true 以及 false。

state meaning
undefined 尚未回答的問題
true 答對的問題
false 答錯的問題

將所有的物件加上狀態屬性 (state):

Object.values(data).forEach(e => e['state'] = undefined)

例如,找出尚未回答過的問題:

Object.values(data).filter(e => e['state'] !== undefined)

如此一來就不需要額外用變數來記錄相關的資訊,重新進行練習也只需要搭配 delete 來清空所有的 Object 狀態 Property 即可。

Object.values(data).forEach(e => delete e['state'])

Refactor

loadAllQues

loadAllQues() {
  this.wrongs = new Set();
  Object.values(quesbank).forEach((e) => delete e.state);
  this.allQues = Object.values(quesbank);
  this.getQues();
},

getQues

getQues(data) {
    stateCount = _ => Object.values(data).filter(e => e['state'] !== undefined).length
    
    while (stateCount() < Object.values(data).length) 
    {
        console.log('Quest Round');
        pick = chance.pickone(
          Object.values(data).filter(e => e['state'] === undefined))
        sets = chance.pickset(
          Object.values(data).filter(e => e !== pick), 3)
        console.log(chance.shuffle([pick, ...sets]));
        pick['state'] = chance.pickone([true, false])
        
        console.log(data);        
    }
}

CheckAns

checkAns(i) {
  if (this.isQuesRoundDone() !== true) {
    const isCorrect = this.ansPools[i] === this.quesObj.meaning;
    if (isCorrect) {
      this.completed += 1;
      this.quesObj.state = true;
    } else {
      this.quesObj.state = false;
    }
    this.playAudio(isCorrect === true);

    this.clickAns = i;
    this.showAns = true;

    updateStorage(this.completed);
  }
},

參考資料

部署 GitHub Page 的方式