iT邦幫忙

2022 iThome 鐵人賽

DAY 17
1
Modern Web

真的好想離開 Vue 3 新手村 feat. CompositionAPI系列 第 17

Day 17: 元件溝通的原則 feat. props & emit

  • 分享至 

  • xImage
  •  

元件溝通的原則

1. 資料由上而下單向傳遞

這在前端框架中,是很常見的管理原則,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 會形成父子元件之間的單向資料綁定,將父元件的狀態或資料,由上而下、單向傳遞給子元件,而每次父元件的狀態或資料更新後,更新的資料會向下「流到」子元件中。

2. 自己的狀態自己改

「所有的狀態或資料,都應該由擁有的元件自己來修改」,所以,在子元件內,Vue 不會讓開發者直接修改收到的 props

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

要注意的是:

  • 當 props 值為基本型別
    當 props 進來的屬性為基本型別,開發者試圖或意外修改它時,Vue 會報警告,而且沒有辦法成功修改
  • 當 props 值為非基本型別
    當 props 進來的屬性為物件或陣列,開發者試圖或意外修改它時,Vue 會偵測到改動,並警告開發者,但開發者還是可以成功修改到物件內的屬性,應該避免這種況。

有什麼好處

讓元件間資料的流向比較單純,方便管理,也更好 debug。
在遵守以上兩個原則的情況下,如果出現非預期錯誤,要檢查的方向很明確,只要往「上」一層一層找,一定能找到賦值出錯的地方。

你可能想修改 props 的情境與解法

有某些情境,會讓我們不小心修改到 props,或者是想要直接修改 props:

  1. 在子層將 props 值綁定 input 的 v-model
    情境:透過 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" />
    
  2. props 傳進來的是 raw data,元件內要針對這筆資料做加工或過濾,取得相對應的資料。
    情境:從父元件傳入一個 todo list 陣列,但這個元件只需要渲染尚未完成的事項。
    解法:應該使用 computed,而且 computed 會響應上層 prop 的改變,自動更新內部的值。
const props = defineProps({
      todos: {
        type: Array,
      },
    });
const undos = computed(() => props.todos.filter(todo.isDone === false;))
  1. props 傳進來的是初始值,在元件只是要作為 local data 使用。
    情境:子元件為一個計數器,每次引用時,都會透過 props 傳入初始值,父元件和子元件中的 counts 彼此不需要連結或響應。
    解法:在元件內宣告一個區域變數,用來儲存這個初始值。
    const props = defineProps({
          initialCounts: {
            type: Number,
            required: true,
          },
        });
    
    const counter = ref(props.initialCounts)
    

結尾

propsemit 是最常用來傳遞資料的方式,但其實不是唯一的方式,還有:provideinject 可以做到跨父子傳遞 (可以爺爺傳孫子、傳曾孫...),但不論使用什麼方式,原則都是不變的。

  1. 資料/狀態由上而下單向傳遞
  2. 元件自己的狀態自己改

那如果有部份資料/狀態,在很多元件都要用到怎麼辦?
例如:有登入功能的網站,會根據使用者的資料,在各個頁面或元件呈現對應的畫面。

想想在這樣的情況下,要怎麼管理元件間資料/狀態的傳遞和修改,一想就很心累,對吧?
這就是狀態管理器(state manager)存在的意義。

至於「狀態」到底是什麼?這個在進入 Pinia (Vue 3 目前默認的狀態管理器) 時會一併提到。

參考資料


上一篇
Day 16: 從 vuejs 原始碼看 v-on 修飾符串接
下一篇
Day 18: v-slot 到底用在哪?從應用學 v-slot 語法
系列文
真的好想離開 Vue 3 新手村 feat. CompositionAPI31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
superyngo
iT邦新手 5 級 ‧ 2023-06-08 15:08:32

第一例解法
<input type="text" v-model="name" />
應更正為
<input type="text" v-model="username" />

我要留言

立即登入留言