這在前端框架中,是很常見的管理原則,Vue 文件 用「One-Way Data Flow」來描述;React 文件 用「The Data Flows Down」來描述,並提到也會被稱為「top-down」或「unidirectional」資料流。
簡單來說,在這樣的原則下,所有的資料或狀態,都會被特定的元件「擁有」,而這份資料或狀態,只有該元件的後代元件可以取得。
This is commonly called a “top-down” or “unidirectional” data flow. Any state is always owned by some specific component, and any data or UI derived from that state can only affect components “below” them in the tree. --React Doc
在 Vue 進行開發,最常透過 props
來做父子元件的資料傳遞。
透過 props
會形成父子元件之間的單向資料綁定,將父元件的狀態或資料,由上而下、單向傳遞給子元件,而每次父元件的狀態或資料更新後,更新的資料會向下「流到」子元件中。
「所有的狀態或資料,都應該由擁有的元件自己來修改」,所以,在子元件內,Vue 不會讓開發者直接修改收到的 props
。
開發者如果想在子元件修改父元件的資料或狀態,最常見的作法,是在父元件進行監聽,等待子元件透過 emit
發送自訂事件,告訴父元件要更新資料或狀態。
要注意的是:
讓元件間資料的流向比較單純,方便管理,也更好 debug。
在遵守以上兩個原則的情況下,如果出現非預期錯誤,要檢查的方向很明確,只要往「上」一層一層找,一定能找到賦值出錯的地方。
有某些情境,會讓我們不小心修改到 props,或者是想要直接修改 props:
<input>
就很自然地把拿到的 user 資料,綁到輸入框的 v-model 上:
<input v-model="userName">
但 v-model 語法糖幫我們做了雙向綁定,會直接修改到傳進來的資料:
<input
:value="userName"
@input="event => userName = event.target.value">
解法:這時候可以用 v-model + computed 的作法,在 computed 的 setter 中 emit 事件。
const props = defineProps({
name: {
type: String,
},
});
const emit = defineEmits(["update:modelValue"]);
const userName = computed({
get() {
return props.name;
},
set(newValue) {
emit("update:modelValue", newValue);
},
});
<input type="text" v-model="name" />
const props = defineProps({
todos: {
type: Array,
},
});
const undos = computed(() => props.todos.filter(todo.isDone === false;))
const props = defineProps({
initialCounts: {
type: Number,
required: true,
},
});
const counter = ref(props.initialCounts)
props
和 emit
是最常用來傳遞資料的方式,但其實不是唯一的方式,還有:provide
和 inject
可以做到跨父子傳遞 (可以爺爺傳孫子、傳曾孫...),但不論使用什麼方式,原則都是不變的。
那如果有部份資料/狀態,在很多元件都要用到怎麼辦?
例如:有登入功能的網站,會根據使用者的資料,在各個頁面或元件呈現對應的畫面。
想想在這樣的情況下,要怎麼管理元件間資料/狀態的傳遞和修改,一想就很心累,對吧?
這就是狀態管理器(state manager)存在的意義。
至於「狀態」到底是什麼?這個在進入 Pinia (Vue 3 目前默認的狀態管理器) 時會一併提到。
第一例解法<input type="text" v-model="name" />
應更正為<input type="text" v-model="username" />