Vue 3 — 在子元件中修改父層的狀態 feat. props、emit、computed、defineModel()

Voss
6 min readJun 4, 2023

--

2024.09.19

沒想過這篇文章能有這麼大的瀏覽量,身為作者的我也應該負起一些責任。所以各位讀者,如果你使用 vue 版本是 3.4 以上的,那麼恭喜你有更加優雅且簡潔的寫法,實現雙向綁定,只需要一個方法 defineModel()

Child Component

<!-- Child.vue -->
<script setup>
const model = defineModel()

function update() {
model.value++
}
</script>

<template>
<div>Parent bound v-model is: {{ model }}</div>
<button @click="update">Increment</button>
</template>

Parent Component

<!-- Parent.vue -->
<Child v-model="countModel" />

Input Component

<script setup>
const model = defineModel()
</script>

<template>
<input v-model="model" />
</template>

更多詳細的用法,可以到官方文檔去查閱。如果版本是 3.4 以下的讀者,建議還是升級,畢竟真的方便太多了。但礙於專案太大或是環境不允許,再請往下觀看,感謝大家的熱情點閱。

前言

在開發過程中,一但 template 太大,一個不順眼就想抽成一個 component 來管理。不論是在可讀性與狀態管理上都更加簡潔優雅。而在這個過程中難免會遇到需要在子層透過 v-model 綁定父層狀態的情況,解法很多種,目前覺得最快速的方式就是 provide 與 inject,但這樣寫的話如果要傳遞的參數一多,就會寫重複的樣板 code,版面也不是這麼好看,不論是父層與子層都會更複雜

直接綁定 props 的參數不就好了嗎?

我也曾經天真可愛,直接將 props 的參數傳進 component 裡面的 input 並透過 v-model 綁定,但系統會提示你這是個危險的行為。畢竟將父層的狀態綁定到子層的元件上,就會破壞了 Vue 注重的 One-Way Data Flow 原則。「所有的狀態或資料,都應該由擁有的元件自己來修改」,所以,在子元件內,Vue 不會讓開發者直接修改收到的 props

正確的起手式

開發者如果想在子元件中修改父元件的資料或狀態,最常見的作法,是在父元件進行監聽,等待子元件透過 emit 發送事件,告訴父元件更新資料或狀態。

總結就是自己實現 v-model ,將 props 與 emits 定義清楚

不過如果只是單純透過 props 與 emits ,還無法做到很漂亮的連動,這邊還需要借助 computed,就讓範例來為大家說明

子元件

可以看到在子元件中,我們是使用一個 computed 與 props 的參數連動,並且在 input 的 v-model 做到綁定,並非直接綁定 props 的參數。再透過 get 與 set 的效果,讓用戶修改 input 時,能馬上 emit 新的 value 給上層

<script setup>
import { computed } from "vue";
const props = defineProps({
value: String
});

const emits = defineEmits(["update:value"]);

const inputValue = computed({
get() {
return props.value;
},
set(newValue) {
emits("update:value", newValue);
}
});
</script>


<template>
<div class="container">
<input v-model="inputValue" />
</div>
</template>

父元件

在父層去傳送一個 ref 狀態物件來綁定剛剛自定義的元件,並透過 <pre> tag 來查看結果,是否能成功透過 v-model 來綁定子元件中的 input 元件

<script setup>
import { ref } from "vue";
import TestInput from "../components/presentation/TestInput.vue";

const data = ref("init data");
</script>


<template>
<div class="container">
<pre> {{ data }} </pre>
<test-input v-model:value="data" />
</div>
</template>

大功告成~!

小結

以上就是筆者我自己參考一些文章與官方文件摸索出來的做法,當然還有其他很多做法,像是上面提到的 provide 與 inject。但不論哪個做法,都應該去遵從單一資料流的原則,與保持狀態的響應性,避免在往後維護專案中的 components 時,搞不清楚之間狀態的層級與連結關係

--

--

Voss

a Freelancer , a Software Engineer , a Reader , a Body Builder