iT邦幫忙

2021 iThome 鐵人賽

DAY 10
1
Modern Web

[ 重構倒數30天,你的網站不Vue白不Vue ] 系列 第 10

[重構倒數第21天] - 五種重構Vue2專案的時候最常看到需要被改善的code

前言

該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。

我常常在重構專案的時候會看到前人寫的一些關於 vue 的 code 讓人摸不著頭緒,以及滿頭問號的情況,今天我將把一些我常常看到覺得不必要的寫法以及可以改善的 code 做個比較,讓大家在寫 code 的時候可以多多注意不要寫出讓人滿頭問號的程式碼。

1. 你的 this 與 arrow function

https://ithelp.ithome.com.tw/upload/images/20210910/20125854JVrN3pJNAE.png
這是我在很多專案常見到的 methods 的寫法

// option api style from vue-cli
methods: {
  init: function() {
    let that = this;
    axios.get("https://test.api/data").then(function(res){
      that.resVal = res.data
    })
  }
}

這個寫法不能說不能 work,只不過不是最理想的寫法,首先

  1. 在 Vue cli 裡面寫 methods 的時候,就可以採用 es6 寫法去掉 :function() 直接 init()就好。

  2. 使用其他第三方工具的時候,call back function 應該使用 ES6 的箭頭函式 ( arrow function ),使用箭頭函式就不需要去外面宣告一個 that 塞入this ,因為箭頭函示內部是沒有 this 的指向,所以會往外部查找,就會直接指向 Vue。

// option api style from vue-cli
methods: {
  init() {
    axios.get("https://test.api/data").then((res)=>{
      this.resVal = res.data
    })
  }
}

2. let 啦!那次不 let !

https://ithelp.ithome.com.tw/upload/images/20210910/20125854Ca3K77T7NF.png
天阿!太多人分不清楚什麼時候應該用 let還是 const,所以乾脆一律 let 到底,我們來看一下什麼東西應該let 什麼應該 const

methods: {
  loginUser (){
    let url = "/api/login";
    let postData = {
      name: this.name,
      password: this.password,
      phone: this.phone,
    };
    axios.post(url, postData).then((res)=> {
		console.log("login success");
    })
  }
}

這是一個很簡單的使用 API post 來進行登入的 function,你會看到這邊的 url 還有要 post 出去的 data 都是使用let 進行宣告,我們來分析一下,首先 let 除了是以大括號為界,具有scoped的效果,也具有可以修改的特性,你可以隨意地修改它裡面的 value ,例如下面這種操作都是可以的

let a = 1;
console.log(a);
a = "mike"
console.log(a);

但是我們有些變數是不需要被修改的,例如我們的範例一樣,我們的 url 就是一個 API 的路徑,它是不會被修改的,所以這邊我們就應該用 const來宣告,而不是 let,我們的 postData 其實也是一樣的,它是最後將所需要的值給組合起來 post 出去的,所以他也應該用 const

const url = "/api/login";
const postData = {
  name: this.name,
  password: this.password,
  phone: this.phone,
};

關於 let 跟 const 的更多差異我相信網路上已經有很多這類的比較資料,這邊我就不再多做贅述。

這邊附上 MDN 的文件,還不了解的可以看一下。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#declarations

3. 你的 props 的 type 呢 ?

https://ithelp.ithome.com.tw/upload/images/20210910/20125854LW8yaUrHzU.png
我看過好多人在寫 Component 的時候 props 都這樣寫,直接用一個 Array 包起來這樣

<script>
export default {
  props: ["usertype", "handleOpenNav", 'count'],
  ...
}
</script>

但是我們可以看到 Vue 官方的 style guide 上面有明確的講說這樣是你在一開始設計 component 的時候用,當你的資料類型都確定完成後,要改成以下寫法。

<script>
export default {
  props: {
    usertype: {
      type: String
    },
    handleOpenNav: {
      type: Function
    },
    count: {
      type: Number
    }
  },
  ...
}
</script>

這樣我們就可以去定義我們傳入的 props 的類型,也可以一眼就知道這個傳入的 props 類型是什麼,即便傳錯了類型的參數,log 也會報錯跟你說參數傳入錯誤了。

props style guide
https://v3.vuejs.org/style-guide/#prop-definitions-essential

然後我最推薦的寫法是除了加上 type 以外,還加入了 default 的處理 props 的方式。

<script>
export default {
  props: {
    usertype: {
      type: String,
      default: "default"
    },
    handleOpenNav: {
      type: Function,
      default: ()=> {}
    },
    count: {
      type: Number,
      default: 0
    }
  },
  ...
}
</script>

你可以看到我給了每一個 props 一個 default value,如果今天我沒有傳入的 props,它就會當成你 default 設定的那個 value 來使用,這樣可以避免掉很多如果你沒有傳入參數的時候,也可以透過這種方式來處理畫面上面的狀態,讓它有預設的 status。

https://v3.vuejs.org/style-guide/#empty-lines-in-component-instance-options-recommended

這邊我列幾個很容易寫錯了的 props default 給大家參考

<script>
export default {
  props: {
    handleOpenNav: {
      type: Function,
      default: ()=> {}
    },
    userList: {
      type: Array,
      default: ()=> ([])
    },
    fetchData: {
      type: Object,
      default: ()=> ({})
    },
  },
  ...
}
</script>

最常看到寫錯的就是 FunctionArrayObject這三個 type,你仔細看 FunctionArray都是需要透過函式的方式去回傳,而不是直接定義一個空的 []或是 {},這點要特別注意。

關於 props 定義的部分可以看這份文件
https://v3.vuejs.org/guide/component-props.html#prop-validation

4. Methods 與 Computed 的糾葛

https://ithelp.ithome.com.tw/upload/images/20210910/20125854ATPMjMjc16.png
我們很常會搞不清楚什麼時候應該用 Computed 什麼時候應該用 Methods 來處理資料的回傳,我們先來看一下Computed 的部分,在我們前一篇的文章有提到

computed 是一個計算屬性,設計它的初衷是用於簡單運算的,在模板中放入太多的邏輯會讓模板過重且難以維護,所以會需要透過computed 來重新處理過那些複雜的資料,官方文件有提到說

computed 屬性是基於Vue綁定的資料依賴關係緩存的

這意味者 computed 只在透過Vue綁定的資料發生改變時它們才會重新去執行處理計算,所以今天你的資料只要是Vue綁定的資料,都可以被計算處理過。

我們來看以下例子

<script>
export default {
  props: {
    showType: {
      type: String,
      default: 'not_login',
    },
  },
  setup(props) {
    const isShowBtnStatus = () => props.showType === "not_login";
    return { isShowBtnStatus };
  },
};
</script>

<template>
  <a v-if="isShowBtnStatus()">登入</a>
  <a v-if="!isShowBtnStatus()">登出</a>
  <a v-if="!isShowBtnStatus()">觀看紀錄</a>
</template>

我們可以看到這邊 isShowBtnStatus 這個 methods去判斷我的 props,然後在 template 的地方去call 這個 methods,讓它回傳狀態,但其實我們應該要改成使用 computed 才對

<script>
import { computed } from "vue";
export default {
  props: {
    showType: {
      type: String,
      default: 'not_login',
    },
  },
  setup(props) {
    const isShowBtnStatus = computed(() => props.showType === "not_login");
    return { isShowBtnStatus };
  },
};
</script>

<template>
  <a v-if="isShowBtnStatus">登入</a>
  <a v-if="!isShowBtnStatus">登出</a>
  <a v-if="!isShowBtnStatus">觀看紀錄</a>
</template>

透過 computed 的方式可以讓資料達到緩存的效果,資料沒變就不會重新計算,除非今天你是要傳參數進去做計算,才需要選擇 methods 來return value.不然的話大部分的計算需求使用 computed 是比較恰當的!

5. Composition Api 跟 Option Api 混用 !?

https://ithelp.ithome.com.tw/upload/images/20210910/201258544FFmeoK06g.png
很多在剛開始升級 Vue3 的朋友會因為還不習慣使用 composition api 而寫出以下的 code

import { ref } from 'vue'
export default {
  props: {
    user: {
      type: String,
      required: true
    }
  },
  data () {
    return {
      filters: {}, 
      searchQuery: ''
    }
  },
  setup (props) {
    const repositories = ref([])
    const handleLog = () => {
      console.log("use")
    }
    
    return {
      repositories,
      handleLog
    }
  },
  watch: {
    searchQuery(){ ... }
  },
  mounted () {
    this.handleLog()
  }
}

你會發現這邊把 Composition Api 跟 Option Api 給混再一起,變得很奇怪,定義data的地方變成了兩個,使用 methods 的時候一下在Option Api 裡面要使用 this 但是在 setup 裡面又不用,整個變得難以閱讀且凌亂,在使用 Composition Api 的時候就可以全部解決這樣的問題。

import { ref, reactive, watch, onMounted } from 'vue'
export default {
  props: {
    user: {
      type: String,
      required: true
    }
  },
  setup (props) {
    const repositories = ref([])
    const searchQuery = ref("")
    const filters = reactive({})
    
    const handleLog = () => {
      console.log("use")
    }
    
    watch(searchQuery, (newVal)=> {
      console.log("newVal=>", newVal)
    })
    
    onMounted(()=> {
    	handleLog()
    })
    
    return {
      repositories,
      handleLog
    }
  }
}

你看我把原本 Option Api 的部分全部改成 Composition Api 是不是清爽許多,官方也是建議 Composition Api 跟 Option Api 則一選擇就好,不推薦兩種寫法混用,導致你的 code 看起來很混亂。

更多關於 Composition Api 的內容
https://v3.vuejs.org/guide/composition-api-introduction.html#introduction

好啦!今天就到這邊啦~明天見!

Mike Vue

那如果對於Vue3不夠熟的話呢?

Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。

我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/bundles/9WwPNYRpz?s=tc

那如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/bundles/b9Rovqy7z?s=tc

訂閱Mike的頻道享受精彩的教學與分享

Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng


上一篇
[重構倒數第22天] - 減少 watch,改用 computed
下一篇
[重構倒數第20天] - i18n什麼的交給前端來處理吧(一) 把GoogleSheet文件轉成JSON文件
系列文
[ 重構倒數30天,你的網站不Vue白不Vue ] 32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
a1740942002
iT邦新手 5 級 ‧ 2021-10-02 21:37:48

Hi 老師~第一點重構完的 code

that.resVal = res.data

應該是寫錯了,要改成

this.resVal = res.data
Mike iT邦新手 3 級 ‧ 2021-10-02 22:53:39 檢舉

在寫的時候沒有注意到,謝謝幫我抓錯誤

我要留言

立即登入留言