Vue3筆記 | 元件與資料傳遞

更新於 2024/11/05閱讀時間約 17 分鐘

Vue 的整個大架構核心是元件 (component),藉由把會重複利用的邏輯、樣式、內容封裝到一個個組件之中,來提高程式碼複用性以及開發效率。簡單來說,一個 Vue 建立的網頁就是由許多組件一個個拼出來的,然後資料在各個組件中穿梭來改變渲染我們的畫面。

所以本篇核心為 Vue 的元件以及資料傳遞。



元件

讓我們先在 src/components 下建立一個 PeopleBlock.vue 的檔案,這之後就會是我們第一個元件。

我們先在 PeopleBlock.vue 中寫一點東西:

<script setup>
</script>

<template>
<h1>I am a component</h1>
</template>

然後回到 App.vue 中來引入剛剛建立的元件,此時 App.vue 相對於 PeopleBlock.vue 來說就是父元件。引入成功會看到畫面改變啦!

<script setup>
import PeopleBlock from './components/PeopleBlock.vue';
</script>

<template>
<PeopleBlock />
</template>
raw-image

這種是元件引入的基本用法,但如果說今天 PeopleBlock.vue 要在很多元件中引入呢?每個元件都寫一次 import好麻煩,所以針對這類情形可以直接修改 main.js。

原本的 main.js 內部長這樣:

import './assets/main.css'

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

createApp(App).mount('#app')

現在我們要把它拆開改成這樣:

import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
import PeopleBlock from './components/PeopleBlock.vue'

const app = createApp(App)
app.component('PeopleBlock', PeopleBlock)

app.mount('#app')

這樣我們可以直接在 App.vue 中直接使用 PeopleBlock.vue 而不用 import



資料傳遞

Props

如果你跟我一樣從 React 過來的,那看到這個名詞會覺得很熟悉,這就是父元件將資料傳給子元件的方式,注意,是父元件傳給子元件

現在假設我們父元件 App.vue 中有一筆資料要傳到 PeopleBlock.vue 中套用它的格式,那麼在兩個檔案中我該如何寫?

App.vue:

<template>
<PeopleBlock name="Jeremy"/>
</template>

PeopleBlock.vue:

<script setup>
const props = defineProps(['name'])
</script>

<template>
<h1>My name is: {{ props.name }}</h1>
</template>

你會發現父元件中給的名字 Jeremy 成功傳給子元件做出了渲染。

raw-image

在 Vue 中靠 defineProps 在子組件中定義接收到父元件資料,可以以陣列或物件的方式呈現。物件的寫法比較嚴謹一些,剛剛的宣告改成物件可以這樣寫:

const props = defineProps({
name:{
type: String,
default: ''
}
})

emit

在講 emit 之前,我要先提一個我稍早跟 senior 討論的問題:到底能不能把 function 在 Vue 中作為 props 傳遞給子元件?

從 React 過來的人應該通常都有這問題吧!因為 React 就是這樣做啊,我為什麼還要用一個 emit 來做傳遞?而且實際操作也可以喔,請看:

App.vue:

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

const name = ref('Jeremy')

const changeName = (newName)=> name.value = newName
</script>

<template>
<PeopleBlock :name="name" :change-name="changeName"/>
</template>

PeopleBlock.vue:

<script setup>
const props = defineProps({
name:{
type: String,
default: ''
},
changeName:{
type: Function,
default: () => {}
}
})
</script>

<template>
<h1>My name is: {{ props.name }}</h1>
<button @click="changeName('Joanna')">click</button>
</template>

會發現實際上當我點擊了按鈕後,名字從 Jeremy 變成 Joanna 了,一切貌似運作正常。而且 Mike 老師也在影片中特別說這樣單向資料流的方式便於維護管理。

但是喔,就是這個但是,Vue 不推薦你這樣做。部分文章也指出這樣做第一個有效能的問題,第二個是 Vue 看心情可能會渲染不出來改變的資料,所以為了避免這種賭運氣的事件,公司的 senior 還是統一用 emit了。

所以現在來看看 Vue 不同於 React 的做法,有一個口訣:

Props in, Events out

Props 大家很熟悉,events out 靠的就是 emit,是由子組件把事件傳給父元件,然後由父組件去監聽這個事件來決定要幹什麼。

比如剛剛的 code,改成 emit 操作會是這樣的:

先改 PeopleBlock.vue:

<script setup>
const props = defineProps({
name:{
type: String,
default: ''
}
})

defineEmits(['update-name'])
</script>

<template>
<h1>My name is: {{ props.name }}</h1>
<button @click="$emit('update-name')">click</button>
</template>

解釋一下就是我在子元件中建立一個客製化事件叫做 update-name,並透過 $emit 將這個事件傳給父元件。

App.vue 修改如下:

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

const name = ref('Jeremy')

const changeName = (newName)=> name.value = newName
</script>

<template>
<PeopleBlock :name="name" @update-name="changeName('Joanna')"/>
</template>

在父元件中透過 @update-name 接收從子元件傳來的事件,若事件被觸發就套用父元件本身設定的 changeName 函式更新名字。

這就是 Vue 有別 React 的資料傳遞方式,這種模式還會在之後的 v-model 在元件中的傳遞也息息相關,是一個必須要搞懂的地方。

v-model

過往在取得表單的資料通常都要寫一大串的 DOM 抓取或是監聽 change 或 input 事件,而在 Vue 中,這些事情變得非常簡單,靠的就是 v-model

我們來建立一下一個 <input> 嘗試看看:

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

const name = ref('Jeremy')
</script>

<template>
<input type="text" v-model="name">
<h1>{{ name }}</h1>
</template>

可以輕鬆發現隨著我在輸入框的改變,<h1> 渲染的畫面也會跟著改變,這表示 v-model 確實地把這個 <input> 跟我的 name 這個 ref 項目綁在一起了,這就是 Vue 在表單處理上強大的地方。

你甚至可以把 v-model 用在 check box、textarea、radio…等地方。

而且 v-model 還提供三種修飾福來幫助處理表單:

  1. .lazy:這個會把 Vue 的同步更新事件從預設的 input 改成 change,也就是你在輸入的時候並不會立即觸發畫面更新。
  2. .number:把輸入資料轉成數字,但如果開頭為0,他會幫你拿掉。
  3. .trim:我感覺最有用的地方,去除首尾空白!再也不用取出input value 後才寫 trim 了!

但是最前面講到的,Vue 是一個元件組成的事件,有時甚至會把這樣一個 <input> 單獨寫成一個元件,那 v-model 勢必得在元件中傳遞,該怎麼做呢?回歸剛剛講到的 Props in, Events out,來看一下如何利用這個口訣解決問題。

先創個 InputArea.vue 元件然後先不寫任何資料:

InputArea.vue:

<script setup >
</script>

<template>
<input type="text">
<h1></h1>
</template>

App.vue:

<script setup>
import InputArea from './components/InputArea.vue'

</script>

<template>
<InputArea/>
</template>

現在我們來建立資料進行傳遞:

App.vue:

<script setup>
import { ref } from 'vue';
import InputArea from './components/InputArea.vue'

const name = ref('Jeremy')
</script>

<template>
<InputArea v-model="name"/>
</template>

父元件的操作很簡單,綁 v-model 就對了。

InputArea.vue:

<script setup >
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
<input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
<h2>{{ modelValue }}</h2>
</template>

我們在子元件上定義接收資料的 props 與傳送事件的 emit,然後在 <input> v-vindvalue 屬性和 input 事件,input 事件中帶上我們自定義的事件,這樣就成功讓 v-model 在兩個元件中傳遞。

那能不能綁定多個 v-model呢?讓我抄一下官網的 code :

App.vue:

<script setup>
import { ref } from 'vue';
import InputArea from './components/InputArea.vue'

const first = ref('Jeremy')
const last = ref('Ho')
</script>

<template>
<InputArea
v-model:first-name="first"
v-model:last-name="last"/>
</template>

InputArea.vue:

<script setup>
defineProps({
firstName: String,
lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
<h1>{{ firstName }} {{ lastName }}</h1>
</template>

你可能注意到,上面兩個 v-model 的範例中,App.vue 中對 v-model 的綁定寫法不太一樣。一個是直接 v-model="變數",一個是 v-model:名字="變數",對於前面那種作法,你在子元件中 defineProps 時就只能用 modelValue,而後面那一種允許你寫自己定義的名字,這是差別所在。



參考資料

  1. Vue.js 官方網站
  2. Vue3 + Vite 快速上手 Get Startrd EP3 - components / props / emit
  3. Vue3 + Vite 快速上手 Get Startrd EP4 - v-model 資料的雙向綁定 / 自訂義組件的資料綁定 / One-Way Data Flow 單向資料流
  4. Vue 中,如何将函数作为 props 传递给组件
  5. 父子組件資料傳遞 props、$emit
avatar-img
18會員
37內容數
這個專題用來存放我在學習網頁開發時的心得及知識。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Jeremy Ho的沙龍 的其他內容
Vue3 學習筆記,專案建立與基礎響應式篇
AWS 佈署簡單操作以及 RDS 建立篇
AWS 部署學習 - Day1,註冊與安裝 EB
記錄一下 API 串接的四種方式 www
有關 git add, git commit, git push
Vue3 學習筆記,專案建立與基礎響應式篇
AWS 佈署簡單操作以及 RDS 建立篇
AWS 部署學習 - Day1,註冊與安裝 EB
記錄一下 API 串接的四種方式 www
有關 git add, git commit, git push
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
在 Vue 應用中直接使用 .reverse() 來修改陣列可能在開發環境中未出現問題,但在生產環境中卻可能導致重新渲染錯誤及資料順序不一致的問題。建議使用 .slice() 創建淺拷貝的解決方案,以確保 Vue 的反應性系統能正常運作並避免應用當機。
Thumbnail
前言 Vue 是一個現代開發框架,擁有完尚的生態系,讓我們可以將須多元件客製化,做出組件,並且可重複利用,高擴充性。在開發組件時,每個組件都擁有自己的生命周期,Vue 組件會偵測每個變數值,是否有變,並且更新內容,今天要一個一個了解 Vue 的生命週期,讓大家有更多認識。 Vue 的生命週期
父元件 傳遞方法使用@ <template>    ...    <Login @modalClose="modalClose"/> ... </template> <script setup>     const _modal = ref();     function m
父元件 傳遞變數時須加上冒號 子元件 接收props用法如下 本筆記參考: 1. https://www.netlify.com/blog/understanding-defineprops-and-defineemits-in-vue-3.2 2. https://juejin.cn/post/7
Thumbnail
雖然距離上次Vue直播班課不到一年,但看到這次的課程有Pinia內容,手又不小心刷了魔法小卡(? 意識到自己的成長應該是可以輕鬆地串接API,畢竟去年也已經串到可以去烤串店了XD,就算是重新複習,還是可以從中獲得新的成長,而且發現到老師一年講的比一年還好,今年有很多觀念講得更清楚了! 可惜這次第六週
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
在 Vue 應用中直接使用 .reverse() 來修改陣列可能在開發環境中未出現問題,但在生產環境中卻可能導致重新渲染錯誤及資料順序不一致的問題。建議使用 .slice() 創建淺拷貝的解決方案,以確保 Vue 的反應性系統能正常運作並避免應用當機。
Thumbnail
前言 Vue 是一個現代開發框架,擁有完尚的生態系,讓我們可以將須多元件客製化,做出組件,並且可重複利用,高擴充性。在開發組件時,每個組件都擁有自己的生命周期,Vue 組件會偵測每個變數值,是否有變,並且更新內容,今天要一個一個了解 Vue 的生命週期,讓大家有更多認識。 Vue 的生命週期
父元件 傳遞方法使用@ <template>    ...    <Login @modalClose="modalClose"/> ... </template> <script setup>     const _modal = ref();     function m
父元件 傳遞變數時須加上冒號 子元件 接收props用法如下 本筆記參考: 1. https://www.netlify.com/blog/understanding-defineprops-and-defineemits-in-vue-3.2 2. https://juejin.cn/post/7
Thumbnail
雖然距離上次Vue直播班課不到一年,但看到這次的課程有Pinia內容,手又不小心刷了魔法小卡(? 意識到自己的成長應該是可以輕鬆地串接API,畢竟去年也已經串到可以去烤串店了XD,就算是重新複習,還是可以從中獲得新的成長,而且發現到老師一年講的比一年還好,今年有很多觀念講得更清楚了! 可惜這次第六週