Vue3筆記 | 元件與資料傳遞

閱讀時間約 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
18會員
37內容數
這個專題用來存放我在學習網頁開發時的心得及知識。
留言0
查看全部
發表第一個留言支持創作者!
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
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
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
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
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,就算是重新複習,還是可以從中獲得新的成長,而且發現到老師一年講的比一年還好,今年有很多觀念講得更清楚了! 可惜這次第六週