iT邦幫忙

2021 iThome 鐵人賽

DAY 2
6
Modern Web

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

[重構倒數第29天] - Vue2 Option API 轉換 Vue3 Composition API

  • 分享至 

  • xImage
  •  

前言

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

Vue3 的 Composition Api 可以說是改變了原本的 Option Api 撰寫上面的方式,還有撰寫 Vue 上面的一些思維的變動。

所以今天如果我們接手了一個 Vue2 的專案要升級成 Veu3 Composition Api 的話,我們可以先從甚麼地方下手,會需要注意那些地方,然後要怎麼去改寫 Option Api,我們馬上來看一下。

1. 先定義 setup 函式

跟以往的 Option Api 不同,我們可以在 setup 函式裡面去定義我們所有的東西,包含datamethodscomputedlifecycle 等方法。

<script>
export default {
    setup(){
        return {}
    }
}
</script>

2. 檢查有多少 data

我們在改寫的時候會先來看一下有多少被定義的資料,這邊是我們在 Option Api 裡面定義的資料的部分。

<script>
export default {
    data(){
        return {
            userData: {
                name: '',
                age: '',
                address: '',
            },
            isOpen: false,
            products: []
        }
    }
}
</script>

我們接下來來用 Vue3 的方式來改寫 ,就會像這樣。

<script>
import { ref, reactive } from "vue";
export default {
  setup() {
      const isOpen = ref(false)
      const products = ref([])
      const userData = reactive({
          name: '',
          age: '',
          address: '',
      })
      return {}
  },
};
</script>

在這邊你會看到我定義資料是用 refreactive 來定義的,關於我們該如何選用 refreactive 呢 ? 在大多數的情況下兩者皆可以互相替換使用,主要取決於個人喜好或是團隊習慣,根據具體情況來決定要使用哪個好,關於 refreactive 的差異我們來看一下 :

  • ref : 可以使用任何型態的資料,但是不會對 Object 或是 Array 內部的屬性做監聽。
  • reactive : 只接受 ObjectArray ,可以做深層的監聽,以及取資料的時候不用使用 .value,但是如果對 reactive 的 Object 使用解構的方式取得內容,就會失去了 Vue 更新綁定的機制。

我自己個人一般的情況下能盡量使用 ref就使用ref,可以更精準地去控制資料的更新,很多人不習慣用 .value的方式,但是 .value的方式可以讓開發者更明確知道這段 code 再進行資料的更新。

更多具體的細節可以參考我之前寫的這篇文章 ref 跟 reactive 我該怎麼選 !?

3. methods 與 this

一般我們要定義 methods 會在 methods 這個欄位裡面去定義它的 function,然後要 getset可以透過 this來使用,這個 this就是指向 Vue 本身。

<script>
export default {
    data(){
        return {
            userData: {
                name: '',
                age: '',
                address: '',
            },
            isOpen: false,
            products: []
        }
    },
    methods: {
        handleTroggle(){
            this.isOpen = !this.isOpen
        },
        setProducts(prod){
            this.products = prod
        },
        showUserKey(){
            this.userData.name = 'mike'
            console.log(this.userData.name)
        }
    }
}
</script>

但是改成 Composition Api 的方式就不需要寫在 methods 裡面,也不需要管 this 指向的部分,整體下來看起來乾淨很多。

<script>
import { ref, reactive } from "vue";
export default {
  setup() {
      const isOpen = ref(false)
      const products = ref([])
      const userData = reactive({
          name: '',
          age: '',
          address: '',
      })
      
      const handleTroggle = () => {
          isOpen.value = !isOpen.value
      }
      
      const setProducts = (prod) => {
          products.value = prod
      }
      
      const showUserKey = () => {
          userData.name = 'mike'
          console.log(userData.name)
      }
      
      return {}
  },
};
</script>

4. 換掉 mixins,把共用邏輯抽出來

以前我們會把共用的 function 或是 data 給放到 mixins,但是 mixins 最讓人詬病的是對於來源的查找很不明確,只能靠 coding style 來做區別,所以我們要透過 Composition Api 的方式來處理共用的 function 。

· 使用 mixin

先新增一個 myMixin.vue的檔案

// myMixin.vue
<script>
    export default {
      data(){
          return {
              point: 0
          }
      },
      methods: {
        addPoint() {
            this.point += 1
        }
      }
    }
</script>

然後再我們的 Component 去載入 myMixin.vue

// app.vue
<script>
import myMixin from "./myMixin.vue";
export default {
    mixins: [myMixin],
    data(){
        ...
    },
    methods: {
        ...
    }
}
</script>
<template>
    <div id="app">
        <h1>Point: {{ point }}</h1>
        <button @click="addPoint">click</button>	
    </div>
</template>

上面就是一般我們使用 mixins 的方式,雖然方便,但是如果多個 Component 的引入跟使用也會造成管理上的不方便,所以接下來我們使用 composition Api 來包裝它。

· 使用 Composition Api

先新增一個名叫 useAddPoints.js 的檔案

import { ref, readonly  } from "vue"
export function useAddPoints(){
    const point = ref(0)
    const addPoint = () => {
        point.value += 1
    }
    return {
  		point: readonly(point),
        addPoint
    }
}

這邊使用了 readonly最主要是希望這個傳出去的值是只可以get讀取的,所以在這邊把它給用 readonly 包起來,關於 readonly 的細節可以參考官方的文件

官方文件 : https://v3.vuejs.org/api/basic-reactivity.html#readonly

然後要使用的話就像是這樣,非常的簡單明瞭,完全解決了使用 mixins上面來源不清楚的問題。

<script>
import { useAddPoints } from "../composition/useAddPoints.js"
export default {
  setup() {
      const { point, addPoint } = useAddPoints();
      
      return {
          point,
          addPoint
      }
  },
};
</script>
<template>
    <div id="app">
        <h1>Point: {{ point }}</h1>
        <button @click="addPoint">click</button>	
    </div>
</template>

5. lifecycle (生命週期) 的更新

我們在 Option Api 裡面有許多的 lifecycle 可以用,但是到了 composition Api 裡面就有了一些變化,所以今天讓我們來稍微看一下這些變化,也好在轉移程式碼的時候避免發生一些問題。

<script>
export default {
    beforeCreate(){},
    created(){},
    beforeMount(){},
    mounted(){},
    updated(){},
    destroyed(){}
}
</script>

但是在 composition Api 裡面我們要從 vue 裡面把 lifecycle 取出來在 setup 使用。

<script>
import { 
    onBeforeMount, 
    onMounted, 
    onBeforeUpdate, 
    onUpdated, 
    onUnmounted 
} from "vue"

export default {
  setup() {
      
      onBeforeMount(()=> {})
      
      onMounted(()=> {})
      
      onBeforeUpdate(()=> {})
      
      onUpdated(()=> {})
      
      onUnmounted(()=> {})
      
      return {}
  },
};
</script>

不過這邊要特別注意,原本 Veu2 裡面 destroyed 這個生命週期函式再 Vue3 裡面 改名叫做 onUnmounted,然後原本的 beforeCreatecreated 沒了, 現在的 setup 這個函式就等同於 beforeCreatecreated 這兩個效果一樣,合再一起了。

更多完整的生命週期函式可以參考官方文件的部份
https://v3.vuejs.org/guide/composition-api-lifecycle-hooks.html#lifecycle-hooks

所以我今天如果是要在 created 裡面去打 API,我就可以直接在 setup 裡面去做操作

<script>
import { ref } from 'vue'
import axios from "axios"
export default {
  setup() {
      const userData = ref([])
      
      // 等同於再 created 執行
      axios.get("https://test.demo.com/api/getUser").then((res)=> {
          userData.value = res.data
      })
		
      return {
          userData
      }
  },
};
</script>

或是今天你要移除原生 JS 監聽的時候,就可以改用 onUnmounted

<script>
import { onMounted, onUnmounted } from 'vue'
import axios from "axios"
export default {
  setup() {
      
      const handleWinReSize = (e) => {
          console.log(e)
      }
      
      onMounted(()=> {
          window.addEventListener("resize", ,handleWinReSize)
      })
      
      // 等同於 Vue2 的 destroyed
      onUnmounted(()=> {
          window.removeEventListener("resize", ,handleWinReSize)
      })
      
      return { }
  },
};
</script>

6. Props 跟 Emit 的使用

以前在 Option Api 上面的時候都要透過 this 的方式來取得 props 還有使用 emit,但是在 Composition Api 裡面可以統一透過 setup 函式來取得這兩個物件。

props

定義好 props 傳入的內容,我們就可以從 setup 正確的取得 props ,它會從 setup 的第一個參數傳入。

<script>
import { onMounted } from 'vue'
export default {
  props: {
     userType: {
         type: String,
         default: "user"
     }  
  },
  setup(props) {
      
      onMounted(()=> {
          console.log(props.userType)
      })
      
      return { props }
  },
};
</script>
<template>
	<h1>{{ props.userType }}</h1>
</template>

emits

emit 再 setup 的第二個參數的物件裡面,我們可以透過解構的方式直接取出,然後使用。

<script>
export default {
  emits: ['userName'],
  setup(props, { emit }) {
      
      const getUserName = (name) => {
          emit("userName", name)
      }
      
      return { getUserName }
  },
};
</script>
<template>
     <button @click="getUserName('mike')">mike</button>
     <button @click="getUserName('jacky')">jacky</button>
     <button @click="getUserName('andy')">andy</button>
     <button @click="getUserName('scars')">scars</button>
     <button @click="getUserName('ash')">ash</button>
</template>

先告一個段落

重構這件事情不是一個小工程,它裡面有很多需要注意的細節在裡面,包括如何讀懂人家的邏輯以及理解這個功能開發上面的前因後果,還有就是你的經驗的展現,各種問題、各種情境,除了程式碼語法版本上面的差異外,還有很多都是要下功夫的,再後面的章節,我會再一一的跟大家介紹關於重構專案上面其他需要注意的地方,還有我的思考規劃的方式,以及選用技術的考量等。

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


上一篇
[重構倒數第30天] - 使用 Vue3 Composition API 重構 JS 選單
下一篇
[重構倒數第28天] - 關於拆分 Components 的學問
系列文
[ 重構倒數30天,你的網站不Vue白不Vue ] 32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
巷中小樹裡
iT邦新手 5 級 ‧ 2022-07-26 16:03:40

請問<script setup> 的使用與內文中的定義

<script>
...
export default {
  setup() {
      const { point, addPoint } = useAddPoints();
      
      return {
          point,
          addPoint
      }
  },
};
</script>

在使用上有什麼差別呢?
內文中的方式像是optionAPI與compositionAPI的結合使用方式
正在compositionAPI學習中..看到這裡有點苦手

Mike iT邦新手 3 級 ‧ 2022-07-26 18:18:39 檢舉

之前回應被隱藏了...

Mike iT邦新手 3 級 ‧ 2022-09-07 06:41:54 檢舉

script setup 跟現在範例的其實是一樣的,只是 這是語法糖,如果不是用語法糖的話,就是我現在demo code的樣子!

0
Mike
iT邦新手 3 級 ‧ 2022-07-26 21:02:56

如果有興趣的話可以看我這場直播,這場直播也有提到  語法糖的部分
Yes

我要留言

立即登入留言