Vue2.0的八種常用傳值方式、父傳子、子傳父、非父子組件傳值(最完整)

Vue2.0 傳值方式:

在Vue的框架開發(fā)的項目過程中,經(jīng)常會用到組件來管理不同的功能,有一些公共的組件會被提取出來。這時必然會產(chǎn)生一些疑問和需求?比如一個組件調(diào)用另一個組件作為自己的子組件,那么我們?nèi)绾芜M行給子組件進行傳值呢?如果是電商網(wǎng)站系統(tǒng)的開發(fā),還會涉及到購物車的選項,這時候就會涉及到非父子組件傳值的情況。當然你也可以用Vuex狀態(tài)管理工具來實現(xiàn),這部分我們后續(xù)會單獨介紹。

引用官網(wǎng)的一句話:父子組件的關(guān)系可以總結(jié)為 prop 向下傳遞,事件向上傳遞。父組件通過 prop 給子組件下發(fā)數(shù)據(jù),子組件通過事件給父組件發(fā)送消息,如下圖所示:

image.png

接下來,我們通過實例來看可能會更明白一些:

1. 父組件向子組件進行傳值props

image.png

父組件:

<template>
  <div>
    父組件:
    <input type="text" v-model="name">
    <br>
    <br>
    <!-- 引入子組件 -->
    <child :inputName="name"></child>
  </div>
</template>
<script>
  import child from './child'
  export default {
    components: {
      child
    },
    data () {
      return {
        name: ''
      }
    }
  }
</script>

子組件:

<template>
  <div>
    子組件:
    <span>{{inputName}}</span>
  </div>
</template>
<script>
  export default {
    // 接受父組件的值
    props: {
      inputName: String,
      required: true
    }
  }
</script>

2. 子組件向父組件傳值$emit,父組件通過子組件的ref屬性獲取值

image.png

子組件:

<template>
  <div>
    子組件:
    <span>{{childValue}}</span>
    <!-- 定義一個子組件傳值的方法 -->
    <input type="button" value="點擊觸發(fā)" @click="childClick">
  </div>
</template>
<script>
  export default {
    data () {
      return {
        childValue: '我是子組件的數(shù)據(jù)'
      }
    },
    methods: {
      childClick () {
        // childByValue是在父組件on監(jiān)聽的方法
        // 第二個參數(shù)this.childValue是需要傳的值
        this.$emit('childByValue', this.childValue)
      }
    }
  }
</script>

父組件:

<template>
  <div>
    父組件:
    <span>{{name}}</span>
    <br>
    <br>
    <!-- 引入子組件 定義一個on的方法監(jiān)聽子組件的狀態(tài)-->
    <child v-on:childByValue="childByValue"></child>
  </div>
</template>
<script>
  import child from './child'
  export default {
    components: {
      child
    },
    data () {
      return {
        name: ''
      }
    },
    methods: {
      childByValue: function (childValue) {
        // childValue就是子組件傳過來的值
        this.name = childValue
      }
    }
  }
</script>

盡管有 prop 和事件,但是有時仍然需要在 JavaScript 中直接訪問子組件。
為此可以使用 ref 為子組件指定一個引用 ID。例如:

<div id="parent">
  <user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// 訪問子組件實例
var child = parent.$refs.profile

當 ref 和 v-for 一起使用時,獲取到的引用會是一個數(shù)組,包含和循環(huán)數(shù)據(jù)源對
應的子組件。
refs 只在組件渲染完成后才填充,并且它是非響應式的。它僅僅是一個直接 操作子組件的 應急方案——應當避免在模板或計算屬性中使用refs。

具體使用方法:

父組件

<template>
  <div id="app">
    {{ a }}
    <button @click="getchild">通過ref得到子組件的值</button>
    <!-- 使用組件 -->
    <three ref="mesage"/>
  </div>
</template>
<script>
 
 
import three from './components/three'
 
export default {
  components:{
    three,
  },
  data(){
    return {
      a:'我是父組件',
    }
  },
  methods:{
    getchild(){
       // 通過this.$refs調(diào)用
      console.log(this.$refs.mesage.mes)
    }
  }
}
</script>
 

子組件:

<template>
    <div class="three">
        {{mes}}
    </div>
</template>
 
<script>
    export default {
      data() {
        return {
            mes:'666'
        };
      }
    };
</script>
 

3. 事件總線 $emit / $on

非父子組件之間傳值,需要定義個公共的公共實例文件bus.js,作為中間倉庫來傳值,不然路由組件之間達不到傳值的效果。這種方法通過一個空的Vue實例作為中央事件總線(事件中心),用它來觸發(fā)事件和監(jiān)聽事件,巧妙而輕量地實現(xiàn)了任何組件間的通信,包括父子、兄弟、跨級。當我們的項目比較大時,可以選擇更好的狀態(tài)管理解決方案vuex。

公共bus.js

//bus.js
import Vue from 'vue'
export default new Vue()

組件A:

<template>
  <div>
    A組件:
    <span>{{elementValue}}</span>
    <input type="button" value="點擊觸發(fā)" @click="elementByValue">
  </div>
</template>
<script>
  // 引入公共的bug,來做為中間傳達的工具
  import Bus from './bus.js'
  export default {
    data () {
      return {
        elementValue: 4
      }
    },
    methods: {
      elementByValue: function () {
        Bus.$emit('val', this.elementValue)
      }
    }
  }
</script>

組件B:

<template>
  <div>
    B組件:
    <input type="button" value="點擊觸發(fā)" @click="getData">
    <span>{{name}}</span>
  </div>
</template>
<script>
  import Bus from './bus.js'
  export default {
    data () {
      return {
        name: 0
      }
    },
    mounted: function () {
      var vm = this
      // 用$on事件來接收參數(shù)
      Bus.$on('val', (data) => {
        console.log(data)
        vm.name = data
      })
    },
    methods: {
      getData: function () {
        this.name++
      }
    }
  }
</script>

4. 利用vuex

image.png

(1)簡要介紹Vuex原理

Vuex實現(xiàn)了一個單向數(shù)據(jù)流,在全局擁有一個State存放數(shù)據(jù),當組件要更改State中的數(shù)據(jù)時,必須通過Mutation進行,Mutation同時提供了訂閱者模式供外部插件調(diào)用獲取State數(shù)據(jù)的更新。而當所有異步操作(常見于調(diào)用后端接口異步獲取更新數(shù)據(jù))或批量的同步操作需要走Action,但Action也是無法直接修改State的,還是需要通過Mutation來修改State的數(shù)據(jù)。最后,根據(jù)State的變化,渲染到視圖上。

(2)簡要介紹各模塊在流程中的功能:

Vue Components:Vue組件。HTML頁面上,負責接收用戶操作等交互行為,執(zhí)行dispatch方法觸發(fā)對應action進行回應。

dispatch:操作行為觸發(fā)方法,是唯一能執(zhí)行action的方法。

actions:操作行為處理模塊,由組件中的 $store.dispatch('action 名稱',data1)來觸發(fā)。然后由commit()來觸發(fā)mutation的調(diào)用 , 間接更新 state。負責處理Vue Components接收到的所有交互行為。包含同步/異步操作,支持多個同名方法,按照注冊的順序依次觸發(fā)。向后臺API請求的操作就在這個模塊中進行,包括觸發(fā)其他action以及提交mutation的操作。該模塊提供了Promise的封裝,以支持action的鏈式觸發(fā)。

commit:狀態(tài)改變提交操作方法。對mutation進行提交,是唯一能執(zhí)行mutation的方法。

mutations:狀態(tài)改變操作方法,由actions中的 commit('mutation 名稱')來觸發(fā)。是Vuex修改state的唯一推薦方法。該方法只能進行同步操作,且方法名只能全局唯一。操作之中會有一些hook暴露出來,以進行state的監(jiān)控等。

state:頁面狀態(tài)管理容器對象。集中存儲Vue components中data對象的零散數(shù)據(jù),全局唯一,以進行統(tǒng)一的狀態(tài)管理。頁面顯示所需的數(shù)據(jù)從該對象中進行讀取,利用Vue的細粒度數(shù)據(jù)響應機制來進行高效的狀態(tài)更新。

getters:state對象讀取方法。圖中沒有單獨列出該模塊,應該被包含在了render中,Vue Components通過該方法讀取全局state對象。
5.Vuex與localStorage
vuex 是 vue 的狀態(tài)管理器,存儲的數(shù)據(jù)是響應式的。但是并不會保存起來,刷新之后就回到了初始狀態(tài),具體做法應該在vuex里數(shù)據(jù)改變的時候把數(shù)據(jù)拷貝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的數(shù)據(jù),取出來再替換store里的state。

let defaultCity = "北京"
try {   
// 用戶關(guān)閉了本地存儲功能,此時在外層加個try...catch
if(!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default
 new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
    try{
        window.localStorage.setItem('defaultCity', JSON.stringify(state.city));   
        // 數(shù)據(jù)改變的時候把數(shù)據(jù)拷貝一份保存到localStorage里面
     } catch(e) {}
  }
}
})

這里需要注意的是:由于vuex里,我們保存的狀態(tài),都是數(shù)組,而localStorage只支持字符串,所以需要用JSON轉(zhuǎn)換:

JSON.stringify(state.subscribeList);   
// array -> string
 
JSON.parse(window.localStorage.getItem("subscribeList"));    
// string -> array 

6. $attrs / $listeners
1.簡介

多級組件嵌套需要傳遞數(shù)據(jù)時,通常使用的方法是通過vuex。但如果僅僅是傳遞數(shù)據(jù),而不做中間處理,使用 vuex 處理,未免有點大材小用。為此Vue2.4 版本提供了另一種方法---- $attrs/ $listeners

$attrs:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),并且可以通過 v-bind="$attrs" 傳入內(nèi)部組件。通常配合 interitAttrs 選項一起使用。

$listeners:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監(jiān)聽器。它可以通過 v-on="$listeners" 傳入內(nèi)部組件接下來我們看個跨級通信的例子:

//index.vue
<template>
    <div>
        <h2>浪里行舟</h2>
        <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"></child-com1>
    </div>
</template>
<script>
import childCom1 from "./childCom1.vue"
export default {
    components: {childCom1},
    data (){
        return {
            foo:"javascript",
            boo: "Html",
            coo: "Css",
            doo: "Vue"
        }
    }
}
</script>
//childCom1.vue
<template>
    <div>
        <p>foo:{{foo}}</p>
        <p>childCom1的$attrs:{{$attrs}}</p>
        <child-com2 v-bind="$attrs"></child-com2>
    </div>
</template>
<script>
import childCom2 from "./childCom2.vue";
export default {
    components:{ childCom2 },
    inheritAttrs:false,//可以關(guān)閉自動掛載到組件跟元素上的沒有在props聲明的屬性
    props: { foo: String },// foo作為props屬性綁定
    created() { console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
}
</script>
//childCom2.vue
<template>
    <div>
        <p>boo:{{boo}}</p>
        <p>childCom2的$attrs:{{$attrs}}</p>
        <child-com3 v-bind="$attrs"></child-com3>
    </div>
</template>
<script>
import childCom3 from "./childCom3.vue";
export default {
    components:{ childCom3 },
    inheritAttrs:false,//可以關(guān)閉自動掛載到組件跟元素上的沒有在props聲明的屬性
    props: { foo: String },// foo作為props屬性綁定
    created() { console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
}
</script>
// childCom3.vue
<template>
  <div> 
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default{
  props: {
    coo: String,
    title: String
  }
};
</script>
image.png

如上圖所示 $attrs表示沒有繼承數(shù)據(jù)的對象,格式為{屬性名:屬性值}。Vue2.4提供了 $attrs ,$listeners 來傳遞數(shù)據(jù)與事件,跨級組件之間的通訊變得更簡單。

簡單來說: $attrs與 $listeners 是兩個對象, $attrs 里存放的是父組件中綁定的非 Props 屬性, $listeners里存放的是父組件中綁定的非原生事件。
7.provide/inject
1.簡介

Vue2.2.0新增API,這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在起上下游關(guān)系成立的時間里始終生效。一言而蔽之:祖先組件中通過provider來提供變量,然后在子孫組件中通過inject來注入變量。provide / inject API 主要解決了跨級組件間的通信問題,不過它的使用場景,主要是子組件獲取上級組件的狀態(tài),跨級組件間建立了一種主動提供與依賴注入的關(guān)系。

2.舉個例子

假設有兩個組件: A.vue 和 B.vue,B 是 A 的子組件

// A.vue
export default{
  provide: {    name: '浪里行舟'  }
}
 
// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name); // 浪里行舟
  }
}

可以看到,在 A.vue 里,我們設置了一個 provide: name,值為 浪里行舟,它的作用就是將 name 這個變量提供給它的所有子組件。而在 B.vue 中,通過 inject 注入了從 A 組件中提供的 name 變量,那么在組件 B 中,就可以直接通過 this.name 訪問這個變量了,它的值也是 浪里行舟。這就是 provide / inject API 最核心的用法。

需要注意的是:provide 和 inject 綁定并不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監(jiān)聽的對象,那么其對象的屬性還是可響應的----vue官方文檔 所以,上面 A.vue 的 name 如果改變了,B.vue 的 this.name 是不會改變的,仍然是 浪里行舟。

3.provide與inject 怎么實現(xiàn)數(shù)據(jù)響應式

一般來說,有兩種辦法:

provide祖先組件的實例,然后在子孫組件中注入依賴,這樣就可以在子孫組件中直接修改祖先組件的實例的屬性,不過這種方法有個缺點就是這個實例上掛載很多沒有必要的東西比如props,methods

使用2.6最新API Vue.observable 優(yōu)化響應式 provide(推薦)

我們來看個例子:孫組件D、E和F獲取A組件傳遞過來的color值,并能實現(xiàn)數(shù)據(jù)響應式變化,即A組件的color變化后,組件D、E、F不會跟著變(核心代碼如下:)

// A 組件 
<div>
      <h1>A 組件</h1>
      <button @click="() => changeColor()">改變color</button>
      <ChildrenB/><ChildrenC/>
</div>
 
......
 
  data() {   
    return
     {
        color: "blue"
     };
  },
  
// provide() {
//   return {
//     theme: {
//       color: this.color //這種方式綁定的數(shù)據(jù)并不是可響應的
//     } // 即A組件的color變化后,組件D、E、F不會跟著變 
//   };
// },
  provide() { 
    return{
      theme: this//方法一:提供祖先組件的實例
    };
  },
  methods: {
    changeColor(color) {  
    if(color) {     
        this.color = color;
    } else{      
        this.color = this.color === "blue"? "red": "blue";
     }
   }
  }
// 方法二:使用vue2.6最新API Vue.observable 優(yōu)化響應式 provide
// provide() {
//   this.theme = Vue.observable({
//     color: "blue"
//   });
//   return {
//     theme: this.theme
//   };
// },
// methods: {
//   changeColor(color) {
//     if (color) {
//       this.theme.color = color;
//     } else {
//       this.theme.color = this.theme.color === "blue" ? "red" : "blue";
//     }
//   }  
// }
// F 組件 
 
<template functional>
  <div class="border2">
    <h3 :style="{ color: injections.theme.color }">F 組件</h3>
  </div>
</template>
 
<script>
export default
 {
  inject: {
  theme: {   
 //函數(shù)式組件取值不一樣     
    default: () => ({})
   }
  }
};
</script>

雖說provide 和 inject 主要為高階插件/組件庫提供用例,但如果你能在業(yè)務中熟練運用,可以達到事半功倍的效果!
8. $parent / $children & ref
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例

$parent / $children:訪問父 / 子實例

需要注意的是:這兩種都是直接得到組件實例,使用后可以直接調(diào)用組件的方法或訪問數(shù)據(jù)。我們先來看個用 ref來訪問組件的例子:

// component-a 子組件
export default
 {
  data () {   
    return
     {
         title: 'Vue.js'
     }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
// 父組件
<template>
  <component-a ref="comA"></component-a>
</template>
 
<script>  
export default
 {
    mounted () {    
        const comA = this.$refs.comA;
        console.log(comA.title);  // Vue.js
        comA.sayHello();  // 彈窗
    }
  }
</script>

不過,這兩種方法的弊端是,無法在跨級或兄弟間通信。

// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>

我們想在 component-a 中,訪問到引用它的頁面中(這里就是 parent.vue)的兩個 component-b 組件,那這種情況下,就得配置額外的插件或工具了,比如 Vuex 和 Bus 的解決方案。

總結(jié)

常見使用場景可以分為三類:

父子通信: 父向子傳遞數(shù)據(jù)是通過 props,子向父是通過 events( $emit);通過父鏈 / 子鏈也可以通信( $parent / $children);ref 也可以訪問組件實例;provide / inject API; $attrs/$listeners

兄弟通信: Bus;Vuex

跨級通信: Bus;Vuex;provide / inject API、 $attrs/$listeners

備注:provide 和 inject 綁定并不是可響應的。然而如果傳入的是一個可監(jiān)聽的對象,那么其對象的屬性還是可響應的

原文鏈接:https://blog.csdn.net/qq_36711388/article/details/82949871

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容