1. 程式人生 > 其它 >vue3.x元件間通訊,實用小技巧都在這裡

vue3.x元件間通訊,實用小技巧都在這裡

本想簡單寫寫,沒想到說清楚已經變成了一篇很長的帖子,歡迎當筆記蒐藏起來。

props / emits 父子元件通訊

props一般負責向子元件傳遞資料
下面是一個簡單的例子,父元件向子元件傳遞了一個title,子元件負責顯示title。

// child-component.vue
<template>
  <h2>{{ title }}</h2>
</template>

<script>
  export default {
    name: "child-component",
    props: ['title'] //可以註冊多個prop
  }
</script>

// 父元件中,使用子元件
<ChildComponent :title="'這是傳遞的Title'"></ChildComponent>

emits主要用於監聽子元件事件
開發過程中子元件可能需要與父級元件進行溝通,這時我們就需要用到emits。下面是一個簡單的例子,點選子元件按鈕時,改變了父元件顯示的資訊。

// child-component.vue
<button @click="$emit('textChange', '子元件被點選了')">點我</button>

// 父元件中,使用子元件
<template>
  <div>
    <div>{{ msg }}</div>
    <ChildComponent @textChange="msg = $event"></ChildComponent>
  </div>
</template>

<script>
  import ChildComponent from "./child-component"
  export default {
    components: {
      ChildComponent
    },
    data() {
      return {
        msg: 'test msg!'
      }
    }
  }
</script>

prop 型別、驗證以及預設值
這三塊內容在官網篇幅還挺長的,感覺上重點卻不多,我們上面註冊props的時候使用的陣列,如:["title"],其實實際開發過程中,為了傳值的清晰,我們用的都是物件,此外我們會指定其型別,驗證規則,以及給出預設值,三者都是可選的,看下面的例子:

export default {
  props: {
    // 基礎的型別檢查 (`null` 和 `undefined` 會通過任何型別驗證)
    propA: Number,
    // 多個可能的型別
    propB: [String, Number],
    // 必填的字串
    propC: {
      type: String,
      required: true
    },
    // 帶有預設值的數字
    propD: {
      type: Number,
      default: 100
    },
    // 帶有預設值的物件
    propE: {
      type: Object,
      // 物件或陣列預設值必須從一個工廠函式獲取
      default() {
        return { message: 'hello' }
      }
    },
    // 自定義驗證函式
    propF: {
      validator(value) {
        // 這個值必須匹配下列字串中的一個
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // 具有預設值的函式
    propG: {
      type: Function,
      // 與物件或陣列預設值不同,這不是一個工廠函式 —— 這是一個用作預設值的函式
      default() {
        return 'Default function'
      }
    }
  }
}

注意:
所有的 prop 都使得其父子 prop 之間形成了一個單向下行繫結:父級 prop 的更新會向下流動到子元件中,但是反過來則不行。這樣會防止從子元件意外變更父級元件的狀態,從而導致你的應用的資料流向難以理解。

另外,每次父級元件發生變更時,子元件中所有的 prop 都將會重新整理為最新的值。這意味著你不應該在一個子元件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制檯中發出警告。
但是,如果prop傳遞的是一個物件或者陣列,我們是可以改變其內部的資料的,同時這個改變會變更物件或陣列本身,從而影響到其所在父元件的狀態。

vue3的新選項emits
vue 3 提供了一個 emits 選項,和現有的 props 選項類似。這個選項可以用來定義一個元件可以向其父元件觸發的事件。emits 選項中列出的事件不會從元件的根元素繼承,也將從$attrs property中移除。

emits 可以是陣列或物件,從元件觸發自定義事件,emits 可以是簡單的陣列,也可以是物件,後者允許配置事件驗證(因為返回false也依舊會繼續呼叫事件,所以作者本人沒理解驗證這塊,有知道的小夥伴歡迎留言)。使用如下:

emits: {
  // 沒有驗證函式
  click: null,

  // 帶有驗證函式
  submit: payload => {
    if (payload.email && payload.password) {
      return true
    } else {
      return false // 返回false事件依舊會繼續被呼叫
    }
  }
}

因為不宣告emits也可以使$emit來呼叫傳遞的事件,所以看似用處不大,但宣告首先可以使程式碼清晰,可以一眼看到向其父元件透傳的事件。同時需要向其父元件透傳原生事件(如:@ckick)的元件來說,不宣告會導致事件被觸發兩次,而宣告可以避免這類問題。

provide / inject 多層巢狀元件通訊

使用場景:
1、多層巢狀元件傳值
2、父子元件相互尋找比較麻煩,如slot


簡單示例:

// 父元件 提供物件
provide: {
  user: 'John Doe'
}

// 子元件,接收物件
inject: ['user'],
created() {
  console.log(`Injected property: ${this.user}`) // > 注入 property: John Doe
}

訪問元件例項 property(用this),我們需要將 provide 轉換為返回物件的函式

// 父元件
data() {
  return {
    userName: 'test name',
  }
},
provide() {
  return {
    user: this.userName
  }
}

響應性注意這裡傳遞的是值,並不是響應式的,官網建議分配一個組合式 API computed property:

// 來自官網的建議
todoLength: Vue.computed(() => this.todos.length)

個人建議直接傳遞一個物件過去,利用指標傳遞來實現響應式。(簡單粗暴)如果非要了解可以關注後續再寫文章中關於 reactive provide/inject 的資訊

$attrs / $parent / $refs 能抓到貓就是好老鼠系列

$attrs 包含了父作用域中不作為元件props或 emits 的 attribute 繫結和事件。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結,並且可以通過v-bind="$attrs"傳入內部元件——這在建立高階的元件時會非常有用。
示例:

// 父元件中
<ChildComponent title="123" titleProp="456"></ChildComponent>

// 子元件中
props: ['titleProp'],
created() {
  console.log(this.$attrs.title); // 123
  console.log(this.$attrs.titleProp); // undefined
  
  console.log(this.title); // undefined
  console.log(this.titleProp); // 123
}

$parent 這是父例項,如果當前例項有的話。簡單粗暴拿著例項各種用。

$refs 一個物件,持有註冊過ref的所有 DOM 元素和元件例項。父元件使用其可以輕易的獲取子元件,並各種使用。

eventBus 事件匯流排vue3不支援了

在絕大多數情況下,不鼓勵使用全域性的事件匯流排在元件之間進行通訊。雖然在短期內往往是最簡單的解決方案,但從長期來看,它維護起來總是令人頭疼。
如果非得要用,可以使用一些外部第三方庫,例如mitttiny-emitter

// eventBus.js
import emitter from 'tiny-emitter/instance'

export default {
  $on: (...args) => emitter.on(...args),
  $once: (...args) => emitter.once(...args),
  $off: (...args) => emitter.off(...args),
  $emit: (...args) => emitter.emit(...args),
}

vuex 和 自定義共享物件

vuex 輕鬆獲取響應式的全域性資料。

業務獨立的元件通訊更推薦的做法是自定義一個js物件,用js物件來儲存共享資料和函式。export是一個物件的話,import之後訪問的是同一個物件,其可以方便的提供資料共享和函式呼叫。此外,如果想要不同的物件可以export一個工廠函式。

總結

  • props / emits 應該是父子元件之間溝通的首選。兄弟節點可以通過它們的父節點通訊。
  • provide / inject允許一個元件與它的插槽內容進行通訊。這對於總是一起使用的緊密耦合的元件非常有用。
  • provide / inject也能夠用於元件之間的遠距離通訊。它可以幫助避免“prop 逐級透傳”,即 prop 需要通過許多層級的元件傳遞下去,但這些元件本身可能並不需要那些 prop。
  • prop 逐級透傳也可以通過重構以使用插槽來避免。如果一箇中間元件不需要某些 prop,那麼表明它可能存在關注點分離的問題。在該類元件中使用 slot 可以允許父節點直接為它建立內容,因此 prop 可以被直接傳遞而不需要中間元件的參與。
  • 全域性狀態管理,比如 Vuex