Vue 3 新功能簡介 - 2

by vivid 17. 二月 2021 04:07

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N210222702
出刊日期: 2021/2/17

在本站《Vue 3 新功能簡介 - 1》文章中,介紹了Vue 3專案的建立方式,在這篇文章中,將要繼續介紹Vue 3所新增的功能。

Composition API

Vue 3版變動最大的、討論最多的主題便是新增了Composition API,提供一種新方式來重複使用程式碼與組織程式碼。在Vue 2.0版時,採用Options API來定義Vue組件的「data」、「methods」、「computed」等屬性,這些屬性並不是真正運作的程式碼,因此常常無法提供開發階段的程式撰寫提示,或輔助你進行型別檢查,同時Vue也需要將這些屬性轉換成真正運作的程式碼。

Composition API可以解決這個問題,將組件的屬性以JavaScript函式的型式開放出來,如此程式碼將會更容易閱讀。值得一提的是:Composition API是一個選擇性的功能,不是用來取代Options API,你可以選擇使用它,或使用原有的Options API來開發Vue應用程式。因此舊版的Vue 2.x專案若要升級到Vue 3.x版時,不至於所有程式都得要重新改寫。

此外若有程式碼要在兩個以上的組件重複使用,Vue2需要透過mixins與scoped slots。Mixins易導致命名衝突問題;scoped slots僅限定在組件中的模版中使用。在Vue 3,同樣可以利用Composition API來達成程式碼重用。

不管你使用Vue CLI或 Vite建立專案,預設範本專案中的組件程式碼就是使用Options API來設計Vue組件。例如底下是Vite專案建立的「HelloWorld.vue」組件:

src\components\HelloWorld.vue

<template>
  <h1> {{ msg }} </h1>
  <button @click = "count++"> count is: {{ count }} </button>
  <p> Edit <code> components/HelloWorld.vue </code> to test hot module replacement. </p>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data() {
    return {
      count: 0
    }
  }
}
</script>

「HelloWorld.vue」組件執行時,每次按下畫面中的按鈕,就會把「count」變數累加一,並將累計的值顯示在按鈕標題上。讓我們改用Composition API來改寫「HelloWorld.vue」,修改「HelloWorld.vue」組件程式碼如下:

src\components\HelloWorld.vue

<template>
  <h1> {{ msg }} </h1>
  <button @click = "count++"> count is: {{ count }} </button>
  <p>
    Edit <code> components/HelloWorld.vue </code> to test hot module replacement.
  </p>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    const count = ref(0);
    return {
      count,
    };
  },
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>


 

在<script>區段使用「import」關鍵字匯入「vue」的「ref」函式。接著在組件匯出的預設物件中,定義一個「setup」函式。「setup」函式會在組件建立時執行,在setup」函式中,叫用「ref」函式宣告一個reactive 屬性(reactive property)。「ref」函式可以封裝基礎型別或物件,並回傳reactive 參考(reactive reference),傳入「ref」函式中的值,將會被放到reactive 參考(reactive reference)的「value」屬性。「setup」函式需要回傳一個物件,並且把要給模板(Template)存取的reactive屬性(reactive properties)、計算屬性(computed properties)、watcher、生命周期函式...等成員定義成此物件的屬性,例如範例中的「count」,如此才以在模版中使用它們。

使用前文介紹的指令「npm run dev」執行網站,「HelloWorld.vue」組件的執行結果和上例相同,請參考下圖所示:

clip_image002

圖 1:使用Composition API設計組件。

在Composition API定義函式

那麼如何在Composition API之中定義函式 ?修改上例「HelloWorld.vue」組件的程式碼,加入一個按鈕,在按下按鈕時,將叫用「doubleNumber」函式:

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <button @click = "count++"> count is: {{ count }} </button>
  <button @click = "doubleNumber"> double count is: {{ double }} </button>
  <p>
    Edit <code> components/HelloWorld.vue </code> to test hot module replacement.
  </p>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    const count = ref(0);
    const double = ref(0);
    function doubleNumber() {
      double.value = count.value * 2;
    }
    return {
      count,
      double,
      doubleNumber,
    };
  },
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>

 

我們在「setup」函式透過「ref」中定義一個「double」屬性,當「doubleNumber」函式被叫用時,我們使用「count.value」取得「ref」物件內部的值,將其值乘以「2」,再放到「double」屬性中。最後記得要在「setup」函式回傳「double」屬性與「doubleNumber」函式,以便讓模版(Template)可以存取它們。這個範例程式的執行結果參考如下:

clip_image004

圖 2:在Composition API定義函式。

Fragments

在Vue 2設計組件的HTML模板時,要求根項目只能有一個。而在Vue 3沒有這個限制了,組件模版中可以有多個根節點,這個功能稱之為Fragments。例如使用Vite建立的「HelloWorld.vue」模版:

<template>
  <h1> {{ msg }} </h1>
  <button @click = "count++">count is: {{ count }} </button>
  <p> Edit <code> components/HelloWorld.vue </code> to test hot module replacement. </p>
</template>


過濾器(Filter)

Vue 3.0已經不再支援過濾器(Filter),若要將Vue 2過濾器(Filter)的程式碼升級到Vue 3,取代的作法是將它們改寫成計算屬性,或是叫用函式來取回運算過的值。讓我們來了解一下使用Composition API來設計計算屬性,修改「HelloWorld.vue」範例程式碼如下:

src\components\HelloWorld.vue

<template>
  <h1> {{ msg }} </h1>
  <h1> {{ newMsg }} </h1>
</template>

<script>
import { computed } from "vue";
export default {
  setup(props) {
    const newMsg = computed({
      get: () => props.msg.toUpperCase(),
    });
    return {
      newMsg,
    };
  },
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>


先匯入「vue」的「computed 」函式,然後在「setup」函式定義一個「newMsg」計算屬性,「setup」函式第一個參數可取得「props」物件,我們叫用「toUpperCase()」函式將「props.msg」所有文字轉換成大寫,指派給「newMsg」計算屬性。這個範例執行的結果請參考下圖所示:

clip_image006

圖 3:使用Composition API來設計計算屬性。

全域屬性(Global Properties)

若計算屬性程式碼需要讓多個組件共用,不想在每一個組件之中重複定義,可將之註冊為全域屬性。例如修改Vite專案中的「main.js」檔案程式碼,在「$filters」物件定義一個「toUpper」方法,可以文字轉大寫:

src\main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

const app = createApp(App)

app.config.globalProperties.$filters = {
  toUpper(txt) {
    return txt.toUpperCase();
  },
};
app.mount('#app')

 

然後在「HelloWorld.vue」組件中,使用「$filters」來叫用「toUpper」方法,參可以下範例程式碼:

src\components\HelloWorld.vue

<template>
  <h1> {{ msg }} </h1>
  <h1> {{ $filters.toUpper(msg) }} </h1>
</template>

<script>
export default {
  setup() {
    return {
    };
  },
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>

 

這個範例執行結果同上圖。

多重「v-model」

組件(Component)中可以利用「v-model」指令來建立組件與「Vue」之間的雙向數據綁定功能,在Vue 3中,一個組件可以有套用多個「v-model」指令。例如以下範例程式碼,我們在專案中「components」資料夾加入一個「NameCard.vue」檔案,並在「NameCard」組件中定義兩個「props」,firstName與lastName:

src\components\NameCard.vue

<template>
    <h1>
      <span> { </span>
      {{ firstName }} , {{ lastName }}
      <span> } </span>
    </h1>
  </div>
</template>

<script>
export default {
  name: "NameCard",
  props: {
    firstName: String,
    lastName: String,
  },
};
</script>

<style>
</style>


 

修改「src\App.vue」檔案程式碼如下,在「App.vue」的模版中使用<name-card>組件:

src\App.vue

 

<template>
  <name-card v-model:firstName = "fName" v-model:lastName = "lName"> </name-card>
  <name-card :firstName = "fName" :lastName = "lName"> </name-card>
</template>

<script>
import NameCard from "./components/NameCard.vue";

export default {
  name: "App",
  components: {
    NameCard,
  },
  data() {
    return {
      fName: "mary",
      lName: "su",
    };
  },
};
</script>


「App.vue」中的這行程式:「v-model:firstName」表示使用「v-model」指令設定「firstName」引數(argument),其值來自於「fName」資料屬性。「v-model:firstName」可以簡寫成「:firstName」。依此類推「v-model:lastName」表示使用「v-model」指令設定「lastName」引數(argument),其值來自於「lName」資料屬性。

這個範例執行的結果請參考下圖所示:

clip_image008

圖 4:多重「v-model」範例。

Vue Router

「vue-router」是Vue.js官方提供的路由管理插件,和Vue.js緊密整合在一起,提供的重要功能包含:

  • l 支援巢狀式路由。
  • l 模組化、以組件為基礎的組態設定 。
  • l 支援路由參數、查詢字串、萬用字元。
  • l 支援過場動畫。
  • l 支援HTML5 history模式或hash模式。

目前我們Vite專案中已經包含兩個組件,其一為「HelloWorld.vue」,目前程式碼如下:

src\components\HelloWorld.vue

<template>
  <h1> {{ msg }} </h1>
  <button @click = "count++"> count is: {{ count }} </button>
  <button @click = "doubleNumber"> double count is: {{ double }} </button>
  <p>
    Edit <code> components/HelloWorld.vue </code> to test hot module replacement.
  </p>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    const count = ref(0);
    const double = ref(0);
    function doubleNumber() {
      double.value = count.value * 2;
    }
    return {
      count,
      double,
      doubleNumber,
    };
  },
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>

 

其二為「NameCard.vue」組件,目前程式碼如下:

src\components\NameCard.vue

 

<template>
    <h1>
      <span> { </span>
      {{ firstName }} , {{ lastName }}
      <span> } </span>
    </h1>
  </div>
</template>

<script>
export default {
  name: "NameCard",
  props: {
    firstName: String,
    lastName: String,
  },
};
</script>

<style>
</style>


我們需要路由系統(router system)來協助切換到不同的頁面。你可以使用「npm」安裝「vue-router@next」插件(Plugins),來啟用路由功能。

npm install vue-router@next

這個命令的執行結果參考如下:

clip_image010

圖 5:安裝「vue-router@next」插件(Plugins)。

在Vue 3改用「createRouter」函式來建立路由,修改「App.vue」程式碼如下,利用<router-link>定義「Name Card」與「Hello World」兩個連結,<router-link>實際上是一個組件(Component),<router-view/> 標籤所在的位置是一個定位點,代表未來透過路由執行的「NameCard.vue」、「HelloWorld.vue」組件標籤所要插入的位置:

src\App.vue

<template>
  <nav>
    <router-link to = "/namecard"> Name Card </router-link>
    <router-link to = "/helloworld"> Hello World </router-link>
  </nav>
  <hr />
  <router-view> </router-view>
</template>

<script>
export default {
  name: "App",
};
</script>

 

修改「main.js」程式碼如下:

src\main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

import { createRouter, createWebHashHistory } from 'vue-router';
import NameCard from "./components/NameCard.vue";
import HelloWorld from "./components/HelloWorld.vue";

const routes = [
  { path: '/namecard', component: NameCard, props: { firstName: "mary", lastName: "Su" } },
  { path: '/helloworld', component: HelloWorld },

];

const app = createApp(App)
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

app.use(router)
app.mount('#app')

 

Vue 3需要使用「createRouter」函式來建立路由。搭配「createWebHashHistory」函式來設定使用history模式,然後叫用「app.use」函式來啟用路由。這個範例程式的執行結果參考如下,點選「Name Card」連結時的畫面:

clip_image012

圖 6:Name Card組件。

點選「Hello World」連結時的畫面:

clip_image014

圖 7:Hello World組件。

Vuex狀態管理庫

「Vuex」是由官方提供的「Vue」 的狀態管理庫(State Management Library),「Vuex」實作了完整的狀態管理設計模式(State Management Pattern),提供一個簡易、集中式狀態異動機制,以標準化的方式來異動全域狀態(Global State)。在「Vuex」之中,狀態是「響應式(reactive)」的,當一個組件異動狀態,其它的組件都可以收到異動通知,並可自動取得最新的值。

我們可以透過「npm」工具程式來下載、安裝「Vuex」插件(Plugins),在命令提示字元視窗中,切換目前工作路徑到專案資料夾,輸入以下指令,安裝「vuex@next」插件:

npm install vuex@next --save

這個命令的執行結果參考如下:

clip_image016

圖 8:安裝「vuex@next」插件。

與Vue 2架構相同,你可以定義「actions」、「getters」、「mutations」來操作Vuex Store,Vue 2與Vue 3的最大差別是建立Vuex Store的語法,Vue 3改用「createStore」函式來建立Store。

參考以下程式碼,在「main.js」叫用「createStore」函式定義store,在「state」函式定義一個「count」屬性初始值為「100」。mutations包含一個「increment」函式,用來將「count」屬性值累加一。最後叫用「app.use」函式啟用Vuex:

src\main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

//Router
import { createRouter, createWebHashHistory } from 'vue-router';
import NameCard from "./components/NameCard.vue";
import HelloWorld from "./components/HelloWorld.vue";

const routes = [
  { path: '/namecard', component: NameCard, props: { firstName: "mary", lastName: "Su" } },
  { path: '/helloworld', component: HelloWorld },

];
const app = createApp(App)
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});
app.use(router)

//Vuex
import { createStore } from 'vuex'
const store = createStore({
  state () {
    return {
      count: 100
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

app.use(store);

app.mount('#app')

 

若要在使用Options API的組件中要使用到Vuex,可改寫「NameCard.vue」組件程式碼如下,在「mounted」生命周期事件中,叫用mutations的「increment」函式將「count」值累加一:

src\components\NameCard.vue

<template>
    <h1> Veux Store Count is : {{ this.$store.state.count}} </h1>
    <h1>
      <span> { </span>
      {{ firstName }} , {{ lastName }}
      <span> } </span>
    </h1>
</template>

<script>
export default {
  name: "NameCard",
  props: {
    firstName: String,
    lastName: String,
  },
  data() {
    return { };
  },
  mounted() {
       this.$store.commit('increment');
  },
};
</script>

<style>
</style>

 

若要在使用Composition API的組件中要使用到Vuex,可改寫「HelloWorld.vue」組件程式碼如下:

src\components\HelloWorld.vue

<template>
  <h1> Veux Store Count is : {{ this.$store.state.count}} </h1>

  <h1>{{ msg }}</h1>
  <button @click = "count++"> count is: {{ count }} </button>
  <button @click = "doubleNumber"> double count is: {{ double }} </button>
  <p>
    Edit <code> components/HelloWorld.vue </code> to test hot module replacement.
  </p>
</template>

<script>
import { onMounted, ref } from "vue";
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    const count = ref(0);
    const double = ref(0);
    function doubleNumber() {
      double.value = count.value * 2;
    }
    onMounted(() => {
      store.commit('increment');
    });

    return {
      count,
      double,
      doubleNumber,
    };
  },
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>

 

這個範例程式的執行結果參考如下,每次點選「Name Card」或「Hello World」連結,就會載入組件並觸發「mounted」生命周期事件,將「count」值累加一:

clip_image018

圖 9:存取Veux Store。

點選「Hello World」連結,將「count」值累加一:

clip_image020

圖 10:存取Veux Store。

「Teleport」組件

「Teleport」也是Vue 3新增的新功能,簡單說明一下它的用法,參考下圖Chrome瀏覽器的除錯畫面,在目前「App.vue」組件執行時,模版的內容,插入「<div id = "app"> </div>」標籤之中:

clip_image022

圖 11:使用「Teleport」之前。

組件的模版有可能因為巢狀結構過於複雜,而在執行階段內嵌在深沉的DOM物件之中,透過「Teleport」組件,可以將它們移動到DOM特定的位置。例如修改「App.vue」程式碼如下:

src\App.vue

 

<template>
  <teleport to = "Body">
    <nav>
      <router-link to = "/namecard">Name Card</router-link>
      <router-link to = "/helloworld">Hello World</router-link>
    </nav>
    <hr />
    <router-view></router-view>
  </teleport>
</template>

<script>
export default {
  name: "App",
};
</script>


在執行階段將「App.vue」的標籤搬動到「Body」標籤下,這個範例程式的執行結果參考如下:

clip_image024

圖 12:使用「Teleport」之後。

Tags:

.NET Magazine國際中文電子雜誌 | JavaScript | VueJS | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List