Vue.js script setup - 2

by Vivid 25. 一月 2023 16:05

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:
N230124502
出刊日期: 2023/1/25

本文將延續本站《Vue.js <script setup> - 1》一文的情境,介紹。Vue 3.2版新增加的<script setup>語法來簡化組合式API(Composition API)「setup」函式的基礎程式碼。這篇文章將介紹如何在<script setup>區段之中定義Props、事件,以及匯出組件成員。

 

使用defineProps定義屬性

在<script setup>區塊中,可以使用「defineProps()」與「defineEmits ()」API。不必匯入任何程式,就可直接在<script setup>區塊中使用這兩個API。其中的「defineProps()」API用來宣告Props;而「defineEmits ()」則用來定義事件。舉例來說,參考以下「MyEmployee.vue」組件範例程式碼,使用「defineProps()」定義了「employeeName」、「age」、「isMarried」props,它們的值來自於父組件(MyHome.vue):

src\components\MyEmployee.vue

<script setup>
const props = defineProps( {
  employeeName: String,
  age: Number,
  isMarried: Boolean,
} );
</script>
<template>
  <p> Employee Name : {{ props.employeeName }} </p>
  <p> Age : {{ props.age }} </p>
  <p> Is Married : {{ props.isMarried }} </p>
</template>
<style scoped>
</style>

 

「defineProps()」是編譯器巨集(compiler macros),只能夠在<script setup>區段之中使用,不必撰寫任何匯入的程式碼。在樣版中使用到Props可以省略「props」字眼,可改寫「MyEmployee.vue」組件程式如下:

src\components\MyEmployee.vue

<script setup>
const props = defineProps( {
  employeeName : String,
  age : Number,
  isMarried : Boolean
} );
</script>
<template>
  <p> Employee Name : {{ employeeName }} </p>
  <p> Age : {{ age }} </p>
  <p> Is Married : {{ isMarried }} </p>
</template>
<style scoped>
</style>

 

從父組件(MyHome.vue)傳遞「employeeName」、「age」、「isMarried」的值:

src\components\MyHome.vue

<script setup>
import MyEmployee  from "@/components/MyEmployee.vue";
</script>
<template>
  <h1> Home </h1>
<MyEmployee employee-name="Mary" :age="50" :isMarried="false"> </MyEmployee>
</template>
<style scoped>
</style>

目前「App.vue」程式如下:
src\App.vue
<script setup>
import MyHome from './components/MyHome.vue'
</script>
<template>
  <main>
      <my-home/>
  </main>
</template>

 

這個範例程式的執行結果參考如下:

 

圖 1:使用props。

defineProps() 與 v-model

Props可以直接搭配「v-model」指令綁定到HTML項目,參考以下「MyHome.vue」組件範例程式碼:

src\components\MyHome.vue

<template>
  <div>
    <label> {{ labelText }} </label>
    <input type="text" v-model="userName" />
  </div>
</template>
<script setup>
import { ref } from "vue";
const props = defineProps( {
  userName: String
} );
const labelText = "Hello";

</script>
<style scoped>
label {
  color : blue;
}
</style>

 

在父組件「App.vue」傳遞「user-name」的值到子組件「MyHome.vue」:

src\App.vue

 

這個範例程式的執行結果參考如下:

 

圖 2:使用「v-model」指令綁定到<Input>項目。

在樣板可以使用「props」變數名稱參考「userName」,我們可將「MyHome.vue」組件的程式改寫如下,可得到相同的執行結果。

src\components\MyHome.vue

<template>
  <main>
      <my-home :user-name="name" />
  </main>
</template>
<script setup>
import MyHome from './components/MyHome.vue'
const name = 'mary'
</script>
<style scoped>
</style>

 

使用defineEmits() 定義事件

「defineEmits()」函式可讓我們為組件定義事件。在以下的這個「MyHome.vue」組件範例中,當「MyHome.vue」組件的按鈕被點選之後,便叫用「showMessage」函式來觸發「sayhello」事件,父組件便可訂閱此事件。在「showMessage」函式使用到「userName」時,要前置「props」字串,例如「props.userName」:

src\components\MyHome.vue

<template>
  <div>
    <label> {{ labelText }} </label>
    <input type = "text" v-model = "props.userName" />
  </div>
</template>

<script setup>
import { ref } from "vue";
const props = defineProps( {
  userName: String
} );
const labelText = "Hello";
</script>

<style scoped>
label {
  color: blue;
}
</style>

 

父組件「App.vue」使用「@」符號註冊「sayhello」事件接聽程式,事件觸發時叫用「showMessage」函式,以透過「console.log」函式印出除錯訊息到瀏覽器除錯視窗:

src\App.vue

<template>
  <div>
    <label> {{ labelText }} </label>
    <input type = "text" v-model = "props.userName" />
       <button v-on:click = "showMessage">
         sayhello
     </button>
  </div>
</template>
<script setup>
const props = defineProps( {
  userName: String
} );

const labelText = "Hello";
const emit = defineEmits( [ 'sayhello' ] )
function showMessage() {
    emit( 'sayhello' , props.userName );
  }
</script>
<style scoped>
label {
  color: blue;
}
</style>

 

 

這個範例程式的執行結果參考如下:

圖 3:註冊事件。

若在模板中要直接觸發事件可建用「$emit」函式,使用到「props.userName」時,可以省略「props」字串,直接簡寫為「userName」,例如以下「MyHome.vue」組件範例程式碼:

src\components\MyHome.vue

<template>
  <main>
      <my-home :user-name = "name"  @sayhello = "showMessage"/>
  </main>
</template>
<script setup>
import MyHome from './components/MyHome.vue'
const name = 'mary'
function showMessage( value ) {
    console.log( 'showMessage' , value );
  }
</script>
<style scoped>
</style>

 

這個範例執行時,若在文字方塊輸入資料,會印出一個警示訊息到瀏覽器除錯視窗,指明「userName」是唯讀的,請參考下圖所示:

圖 4:警示訊息。

這是因為子組件「MyHome.vue」不應該直接修改從父組件「App.vue」傳遞而來的props,若要解決這個問題,可將「MyHome.vue」組件程式改寫如下,讓<input>項目綁定到一個ref常數(inputText),然後將「props.userName」值指派給「inputText」;

src\components\MyHome.vue

<template>
  <div>
    <label> {{ labelText }} </label>
    <input type = "text" v-model = "props.userName" />
       <button v-on:click = "$emit( 'sayhello' , userName )">
         sayhello
     </button>

  </div>
</template>

<script setup>
const props = defineProps( {
  userName: String
} );
const labelText = "Hello";
const emit = defineEmits( [ 'sayhello' ] )
</script>
<style scoped>
label {
  color: blue;
}
</style>

 

再執行組件,這次在文字方塊輸入資料將不會顯示警示訊息,請參考下圖所示:

圖 5:不顯示警示訊息。

最後修改父組件「App.vue」程式碼,將子組件「MyHome.vue」變更的「props.userName」值傳遞到父組件後顯示在下方:

src\App.vue

<template>
  <main>
    <my-home :user-name = "name" @sayhello = "showMessage" />
    <div>
      {{ msg }}
    </div>
  </main>
</template>

<script setup>
import { ref } from "vue";
import MyHome from "./components/MyHome.vue";
const name = "mary";
const msg = ref( "" );
function showMessage( value ) {
  msg.value = value
}
</script>
<style scoped>
</style>

 

這個範例程式的執行結果參考如下:

圖 6:顯示更新的Props值。

若在模板撰寫「sayhello」事件接聽程式時,直接使用箭頭函式語法,那存取到「msg」時,不必透過「ref」物件的「value」屬性:

src\App.vue

<template>
  <main>
    <my-home :user-name = "name" @sayhello = "(v) => msg = v " />
    <div>
      {{ msg }}
    </div>
  </main>
</template>
<script setup>
import { ref } from "vue";
import MyHome from "./components/MyHome.vue";
const name = "mary";
const msg = ref("");
</script>
<style scoped>
</style>

 

使用defineExpose() 匯出成員

預設<script setup>區塊中定義的成員都無法直接在其它組件中存取,你需要明確透過「defineExpose()」API匯出成員才可以使用模板引用(Template Refs)方式存取組件的成員。例如以下「MyHome.vue」組件<script setup>區塊程式碼明確叫用「defineExpose()」函式匯出屬性:

src\components\MyHome.vue

 

<script setup>
import { ref } from 'vue'

const employeeName = 'mary'
const age = ref(30)
const isMarried = ref( true )

defineExpose( {
  employeeName,
  age,
  isMarried
} )
</script>
<template>
  <h1> Home </h1>
</template>

 

在父組件「App.vue」模板內子組件的開頭標籤使用「ref」設定模板引用(Template Refs),就可取回子組件傳回的 { employeeName,  age,  isMarried } 物件,接著就可以直接在父組件模板內使用,參考以下範例程式碼:

src\App.vue

<script setup>
import MyHome from "./components/MyHome.vue";
import { ref } from "vue";
const myhomeref = ref( null );
</script>
<template>
  <main>
    <my-home ref = "myhomeref"> </my-home>
    <p>
      Employee :
      {{ myhomeref }}
    </p>
    <p>
      Employee Name :
      {{ myhomeref?.employeeName }}
    </p>
    <p>
      Age :
      {{ myhomeref?.age }}
    </p>
    <p>
      Is Married :
      {{ myhomeref?.isMarried }}
    </p>
  </main>
</template>

 

 

這個範例程式的執行結果參考如下:

 

 

圖 7:使用defineExpose() 匯出成員。

我們也可以在父組件定義計算屬性,來取得子組件匯出的屬性,修改父組件「App.vue」程式如下:

src\App.vue

<script setup>
import MyHome from "./components/MyHome.vue";
import { ref, computed } from "vue";
const myhomeref = ref( null );
const employeeName = computed( () => myhomeref.value?.employeeName );
const age = computed( () => myhomeref.value?.age );
const isMarried = computed( () => myhomeref.value?.isMarried );
</script>
<template>
  <main>
    <my-home ref = "myhomeref"> </my-home>
    <p>
      Employee :
      {{ myhomeref }}
    </p>
    <p>
      Employee Name :
      {{ employeeName }}
    </p>
    <p>
      Age :
      {{ age }}
    </p>
    <p>
      Is Married :
      {{ isMarried }}
    </p>
  </main>
</template>

 

這個範例程式的執行結果同上例。

頂層await(Top-level await)

在<script setup>之中使用「await」關鍵字,可支援非同步呼叫,<script setup>區塊會被編譯成非同步的setup函式(async setup()),參考以下「MyHome.vue」組件範例程式碼,使用「await」關鍵字搭配「fetch」API下載遠端jsonplaceholder伺服器提供的JSON資料:

src\components\MyHome.vue

<script setup>
import { ref } from "vue";
const lists = ref( [] );

const result = await fetch (
  `https://jsonplaceholder.typicode.com/posts?_limit=5`
);
lists.value = await result.json();
</script>

<template>
  <h1> Posts </h1>
  <div v-if = "lists.length" >
    <div v-for = "item in lists" :key = "item.id">
      {{ item.id }} - {{ item.title }}
    </div>
  </div>
</template>

 

async setup() 函式需要和<Suspense>項目一起使用,以便讓Vue處理非同步的細節,然後正確地載入組件,因此需修改父組件「App.vue」的程式如下:

src\App.vue

<script setup>
import MyHome from './components/MyHome.vue'
</script>
<template>
  <main>
    <Suspense>
      <my-home/>
    </Suspense>
  </main>
</template>

不過,在本文撰寫時<Suspense>尚屬於實驗階段功能,建議先不要使用它。

Tags:

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

NET Magazine國際中文電子雜誌

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

月分類Month List