Vue 組件間通訊 12 種方法彙總

自檢清單.png

補充一個 slot 傳值。html

父組件向子組件傳值

1. 經過屬性傳值 props

props 能夠是數組或對象,用於接收來自父組件的數據。vue

// 父組件 List.vue
<template>
  <div>
    <List-item :str="str" :obj="obj" :arr="arr"></List-item>
  </div>
</template>
<script>
import ListItem from "./ListItem";
export default {
  data() {
    return {
      str: "給子組件傳值",
      obj: {msg: "給子組件傳值"},
      arr: [1, 2, 3]
    }
  },
  components: {
    ListItem
  }
}
</script>

// 子組件 ListItem.vue
<template>
  <div>
    <div>{{msg}}</div>
    <div>{{obj}}</div>
    <div>{{arr}}</div>
  </div>
</template>
<script>
export default {
  props: {
    msg: String, // props是字符串
    obj: Object, // props是對象
    arr: Array   // props是數組
  }
}
</script>

2. 使用修飾符 .sync

修飾符 .sync2.3.0+ 新增,它對 props 起到了一種修飾的做用,使用 .sync 進行修飾的 props 意味子組件有修改它的意圖,這種狀況下它只起到一個標註性做用,有它沒它都不會影響邏輯(後文會介紹使用 .sync 的其餘做用)。web

使用 .sync 修改上邊的代碼:vuex

// 父組件 List.vue
<template>
  <!-- 這裏不寫 .sync 也不會影響結果 -->
  <List-item :title.sync="title" @update:title="updataTitle"></List-item>
</template>
<script>
import ListItem from "./ListItem";
export default {
  data() {
    return {
      title: "我是title",
    }
  },
  components: {
    ListItem
  },
  methods: {
   updataTitle(res) {
    this.title = res;
   }
  }
}
</script>

// 子組件 ListItem.vue
<template>
  <div>
    <button @click="handleClick">Click me</button>
    <div>{{title}}</div>
  </div>
</template>
<script>
export default {
  props: {
    title: String, 
  },
  methods: {
   handleClick() {
    // 子組件向父組件傳值
    this.$emit('update:title', '我要父組件更新 title');
   }
  }
}
</script>

使用.sync 向子組件傳遞 多個props:api

當咱們用一個對象同時設置多個 prop 的時候,也能夠將這個 .sync 修飾符和 v-bind 配合使用:數組

<text-document v-bind.sync="doc"></text-document>

這樣會把 doc 對象中的每個屬性 (如 title) 都做爲一個獨立的 prop 傳進去,而後各自添加用於更新的 `v-on 監聽器。微信

更多介紹,.sync網絡

3. 經過 $parent 獲取父組件實例的方法或者屬性

這種方式,從嚴格意思上講不是值的傳遞,而是一種"取"(不推薦直接經過實例進行值的獲取)。app

能夠經過 Vue 的實例屬性 $parent 得到父組件的實例,藉助實例能夠調用父實例中的方法,或者獲取父實例上的屬性,從而達到取值的目的。dom

// 父組件 List.vue
...
<script>
export default {
  data() {
    return {
      message: "hello children",
      msg: "hello"
    }
  },
  methods: {
    sendMessage() {
      return this.message;
    }
  }
}
</script>

// 子組件 ListItem.vue
<template>
  <div>
    <div>{{data}}</div>
    <div>{{msg}}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "",
      msg: ""
    }
  },
  mounted() {
    this.data = this.$parent.sendMessage(); // 調用父實例中的方法
    this.msg = this.$parent.msg; // 獲取父實例中的屬性
  }
}
</script>

拓展

子組件調用父組件中的方法:

  • 經過 $parent 獲取父實例 this.$parent.event
  • 經過 props 傳遞方法。
  • 經過 $emit 監聽父組件中的方法 this.$emit("envnt")

子組件向父組件傳值

1. 經過事件傳值 $emit

  • 子組件使用 $emit 發送一個自定義事件,事件名稱是一個字符串。
  • 父組件使用指令 v-on 綁定子組件發送的自定義事件。
// 父組件 List.vue
<template>
  <div>
    <!-- 監聽自定義事件 -->
    <List-item v-on:welcome="getWelcome"></List-item>
  </div>
</template>
<script>
import ListItem from "./List-item";
export default {
  components: {
    ListItem
  },
  methods: {
    getWelcome(data) {
      alert(data)
    }
  }
}
</script>

// 子組件 ListItem.vue
<template>
  <button @click="handleClick">Click me</button>
</template>
<script>
export default {
  methods: {
    handleClick() {
   // 使用 $emit 發送自定義事件 welcome
      this.$emit('welcome', 'hello');
    }
  }
}
</script>

2. 經過 $children 獲取子組件實例

此方式同 $parent,這裏就不進行介紹了。

3. 經過 ref 註冊子組件引用

儘管存在 prop 和事件,有的時候你仍可能須要在 JavaScript 裏直接訪問一個子組件。爲了達到這個目的,能夠經過 ref 特性爲這個子組件賦予一個 ID 引用。

<template>
  <div>
    <List-item ref="item" :title="title"></List-item>
    <div>{{data}}</div>
  </div>
</template>
<script>
import ListItem from "./List-item";
export default {
  data() {
    return {
      title: "我是title",
      data: ""
    }
  },
  components: {
    ListItem
  },
  mounted() {
    this.data = this.$refs.item.message;
  }
}
</script>

更多 ref 用法

兄弟組件傳值

1. Bus 中央事件總線

非父子組件傳值,可使用一個空的 Vue 實例做爲中央事件總線,結合實例方法 $on$emit 完成傳值操做。

Bus 的定義方式有如下三種:

  1. Bus 抽離出來,組件有須要時進行引入。

    // Bus.js
    import Vue from 'vue'
    const Bus = new Vue()
    export default Bus
  2. Bus 掛載到 Vue 根實例的原型上。

    import Vue from 'vue'
    Vue.prototype.$bus = new Vue();
  3. Bus 注入到 Vue 根對象上。

    import Vue from 'vue'
    const Bus = new Vue()
    new Vue({
      el:'#app',
      data: {
        Bus
      }  
    })

下面案例中的 Bus 掛載在 Vue 原型上:

// 組件1 使用 $emit 向外部發布自定義事件
<template>
  <button @click="handleClick"> Send Message</button>
</template>
<script>
export default {
  data() {
    return {
      message: "給兄弟組件傳值",
    }
  },
  methods: {
    handleClick() {
      this.$Bus.$emit("sendMessage", this.message)
    }
  }
}
</script>

// 組件2 使用 $on 訂閱外部發布的事件
<template>
  <div>
    {{data}}
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "",
    }
  },
  mounted() {
    this.$Bus.$on("sendMessage", data => {
      this.data = data;
    })
  }
}
</script>

注意:註冊的 Bus 要在組件銷燬時卸載,不然會屢次掛載,形成觸發一次但多個響應的狀況。

beforeDestroy () {
  this.$Bus.$off('sendMessage', this.message);
}

2. Vuex 狀態管理器

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

圖片引用自網絡:

vuex

Vuex 的具體使用

3. 經過父組件進行過渡

不是方法的方法:

  1. 子組件 A 經過事件 $emit 傳值傳給父組件。
  2. 父組件經過屬性 props 傳值給子組件 B

深層次嵌套組件傳值

1. 依賴注入 provide/inject

provide 選項容許咱們指定咱們想要提供給後代組件的數據/方法。

provide: function () {
  return {
    getMap: this.getMap
  }
}

而後在任何後代組件裏,咱們均可以使用 inject 選項來接收指定的咱們想要添加在這個實例上的屬性:

inject: ['getMap']

provideinject 主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中。

// 父級組件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子組件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}
然而,依賴注入仍是有負面影響的。它將你應用程序中的組件與它們當前的組織方式耦合起來,使重構變得更加困難。同時所提供的屬性是非響應式的。這是出於設計的考慮,由於使用它們來建立一箇中心化規模化的數據跟使用 $root 作這件事都是不夠好的。若是你想要共享的這個屬性是你的應用特有的,而不是通用化的,或者若是你想在祖先組件中更新所提供的數據,那麼這意味着你可能須要換用一個像 Vuex 這樣真正的狀態管理方案了。

2. $attrs/inheritAttrs

這個兩個屬性是 2.4 新增的特性。

$attrs:

官網介紹的很累贅,暫且理解爲非 props 屬性集合。更多介紹

當一個組件中沒有聲明任何 prop 時,this.$attrs 能夠獲取到全部父做用域的屬性綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳給其內部組件 —— 在建立高級別的組件時很是有用。

inheritAttrs:

控制元素屬性是否顯示在 dom 上,默認值爲 true

默認狀況下父做用域的不被認做 props 的特性綁定 (attribute bindings) 將會「回退」且做爲普通的 HTML 特性應用在子組件的根元素上。當撰寫包裹一個目標元素或另外一個組件的組件時,這可能不會老是符合預期行爲。經過設置 inheritAttrs 到 false,這些默認行爲將會被去掉。而經過 (一樣是 2.4 新增的) 實例屬性 $attrs 可讓這些特性生效,且能夠經過 v-bind 顯性的綁定到非根元素上。

祖先組件:

<template>
  <div>
    <List-item :title="title" :message="message"></List-item>
  </div>
</template>
<script>
import ListItem from "./List-item";
export default {
  data() {
    return {
      title: "我是title",
      message: "傳給後代"
    }
  },
  components: {
    ListItem
  }
}
</script>

父組件:

<template>
  <div>
    <h1>{{title}}</h1>
    <h2>{{$attrs.message}}</h2>
    <!-- 經過 v-bind="$attrs" 傳入後代組件-->
    <ListItem2 v-bind='$attrs'></ListItem2>
  </div>
</template>
<script>
import ListItem2 from './List-item2'
export default {
  props: {
    title: String
  },
  components: {
    ListItem2
  },
  // 默認爲 true,若是傳入的屬性子組件沒有 prop 接受,就會以字符串的形式做爲標籤的屬性存在 <div message="傳給後代"></div>
  // 設爲 false,在 dom 中就看不到這些屬性 <div>...</div>
  inheritAttrs: false
}
</script>

後代組件:

<template>
  <div>
    {{$attrs.message}}
  </div>
</template>
<script>
export default {
  mounted() {
    console.log(this.$attrs)    // {message: "傳給後代"}
  }
}
</script>

渲染出來的結果爲:

$attrs/inheritAttrs

插槽 slot 與子組件傳值

在實際項目中確實有遇到插槽後備內容 動態顯示的狀況,因此這裏要補充一下插槽 後備內容 是如何與子組件進行通訊的。

插槽後備內容是指:寫在父組件中,包含在子組件標籤裏的,與子組件中的 slot 對應。

<template>
  <child-component>
    我是插槽的後備內容
  </child-component>
</template>

好比這裏有一個含有 slotcurrent-user 組件,它的模版結構是這樣的:

<!-- 子組件 current-user.vue -->
<template>
  <div>
    <div>current-user組件</div>
    <slot>插槽裏默認顯示:{{user.firstName}}</slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        firstName: "zhao",
        lastName: "xinglei"
      }
    }
  }
}
</script>

它的父組件是這樣的:

<!-- 父組件 Users.vue -->
<template>
  <div>
    <div>我是Users組件</div>
    <current-user>
      我是插槽裏的後備內容: {{user.lastName}}(我想顯示爲子組件中 user.lastName )
    </current-user>
  </div>
</template>

<script>
import CurrentUser from './Current-User.vue'
export default {
  components: {
    CurrentUser
  }
}
</script>

咱們看到,在父組件 Users 中,爲子組件 current-user 提供的後備內容中,想要顯示子組件定義的 user.firstName 是不能作到的。

官網中提供一個指令 v-slot,它與 props 結合使用從而達到插槽後備內容與子組件通訊的目的。

咱們首先須要在子組件的 slot 中傳遞一個 props(這個props 叫作插槽props),這裏咱們起名叫 user

<!-- 子組件 current-user.vue -->
<template>
  <div>
    <div>current-user組件</div>
    <slot :user="user">
      插槽裏默認顯示:{{user.firstName}}
    </slot>
  </div>
</template>

在父組件中,包含插槽後備內容的子組件標籤上咱們綁定一個 v-slot 指令,像這樣:

<template>
  <div>
    <div>我是Users組件</div>
    <!-- slotProps裏的內容就是子組件傳遞過來的 props -->
    <!-- "user": { "firstName": "zhao", "lastName": "xinglei" } -->
    <current-user v-slot="slotProps">
      {{slotProps}}
    </current-user>
  </div>
</template>

最後渲染出來的結果爲:

做用域插槽

官網給這種插槽起名叫作做用域插槽更多瞭解

總結

1. 組件之間傳值無非就是經過屬性、事件和操做 Vue 實例進行的。
2. 操做實例進行組件件通訊,實例屬性 $root、$parent、$children 分別對應了根實例、父實例、子實例。
3  ref 子組件引用,在操做表單元素時會應用的到。
4. Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式,簡單的應用不要使用 Vuex。
5. Vue.observable() 讓一個對象可響應,能夠做爲最小化的跨組件狀態存儲器(本文未提到)。
相關文章
相關標籤/搜索