Vue組件之間的數據傳遞(通訊、交互)詳解

默認狀況下數據不能在組件之間共享,可是能夠經過如下幾種方法實現組件之間的數據傳遞:vue

props

父子組件之間的數據傳遞:vuex

  • 父傳子
  • 子傳父

父傳子數組

  • 肯定父中有數據
  • 在父組件的模板中經過屬性綁定,把數據綁到子組件上
  • 在子組件中定義props屬性,用來接收父傳遞過來的數據
  • 在子組件的模板就能夠直接使用接收過來的數據

父綁定數據,子接收數據緩存

詳細的格式:安全

Props:{
	數據項名字:{
		type:類型。指明從父組件中傳遞過來的數據必須是什麼類型。它的取值是:Object,Array,String,Number,Boolean 都是構造器。不要寫成字符串  
		default://默認值。當父組件沒有傳數據時,就用這個值  
		required:true/false 。是否必須必定要傳遞過來
	}
}
複製代碼

子傳父bash

  • 在父組件的模板中,給子組件添加一個事件監聽
  • 在子組件中,某個時間經過this.$emit發出這個事件,發出事件的同時能夠攜帶數據 (this.$emit("事件名",附加的數據))
  • 當父中的方法觸發,數據做用與這個方法的第一個參數

父傳子傳孫,只能一級一級的傳,不能跨級傳app

示例:dom

<body>
    <!-- ******************************************************************** -->
    <!-- 父傳子 -->
    <!-- <div id="app">
        <h1>父組件 ->數據:{{num}}</h1>
        <hr>
        <son :sonnum="num" :sonname="name"></son>
    </div>

    <template id="son">
        <div>
            子組件 -> 數據:{{mysum}} -> {{sonname}}
            <button @click="sonnum=200">修改數據爲200</button>
            <h2>{{mysum}}</h2>
            <button @click="mysum=200">修改數據爲200</button>
        </div>
    </template> -->
    <!-- ******************************************************************** -->

    <!-- 子傳父 -->
    <div id="app">
        <h1>父組件</h1>

        <son @submitmsg="addmsg"></son>
        <h2>{{a}}</h2>
    </div>

    <template id="son">
        <div>
            <h3>子組件</h3>
            <button @click="fashe">發射</button>
        </div>
    </template>


    <script>
        //  子傳父************************************************************************
        let Son = {
            template: "#son",
            data() {
                return {
                    num:111,
                }
            },
            methods: {
                fashe() {
                    this.$emit("submitmsg", this.num)
                }
            }
        }
        let vm = new Vue({
            el: "#app",
            data: {
                a: 0,
            },
            methods: {
                addmsg(info) {
                    this.a = info
                }
            },
            components: {
                Son,
            }
        })

//  父傳子*************************************************************************       
        // let Son = {
        //     template: "#son",
        //     data() {
        //         return {
        //             mysum: this.sonnum,

        //         }
        //     },
        //     props: {
        //         sonnum: Number,
        //         sonname: {
        //             type: String,
        //             // required:true,
        //             default: "jun",
        //         }
        //     },
        //     methods: {

        //     }
        // }
        // let vm = new Vue({
        //     el: "#app",
        //     data: {
        //         num: 100,
        //         name: "fan"
        //     },
        //     methods: {

        //     },
        //     components: {
        //         Son,
        //     }
        // })
//********************************************************************************
    </script>
</body>
複製代碼

這麼多東西,相信你也懶得看,你能夠本身建一個文件,複製到裏面測試一下異步

$attrs

若是想要把父組件的數據傳遞給子子組件,若是使用props綁定來進行信息的傳遞,顯然是比較繁瑣的
爲了解決該需求,引入了 $attrside

$attrs 能夠收集父組件中的全部傳過來的屬性除了那些在組件中沒有經過 props 定義的

示例:
首先有三個組件A-B-C,而後想A中的屬性傳入C中,基本的作法是這樣的,一層一層經過 props 往下傳遞

<template>
  <div id="app">
    A{{msg}}
    <component-b :msg="msg"></component-b>
  </div>
</template>
<script>
let vm = new Vue({
  el: "#app",
  data: {
    msg: "100"
  },
  components: {
    ComponentB: {
      props: ["msg"],
      template: `<div>B<component-c :msg="msg"></component-c></div>`,
      components: {
        ComponentC: {
          props: ["msg"],
          template: "<div>C{{msg}}</div>"
        }
      }
    }
  }
});
</script>
複製代碼

ComponentB 組件中並無使用到父組件傳遞過來的屬性 msg,可是這樣寫就是想把屬性再傳遞給ComponentC,那麼除了這種寫法還能夠給ComponentC綁定$attrs屬性。

<script>
let vm = new Vue({
  el: "#app",
  data: {
    msg: "100"
  },
  components: {
    ComponentB: {
      inheritAttrs: false,
      template: `<div>B<component-c v-bind="$attrs"></component-c></div>`,
      components: {
        ComponentC: {
          props: ["msg"],
          template: "<div>C{{msg}}</div>"
        }
      }
    }
  }
});
</script>
複製代碼

這樣就能夠很方便的作到數據傳遞,使用起來也比較簡單,避免多寫 props 的痛苦

通常在使用 $attrs 時,都會加上 inheritAttrs:false 它的做用就是沒有用到的數據,就不會顯示在DOM結構上

$listeners

說完了 $attrs,知道了怎麼把數據從A傳遞給C,那麼此時咱們又想到了一個問題,怎麼把C組件的信息同步到A組件呢? 這時就用到了 $listeners

當組件的根元素不具有一些DOM事件,可是根元素內部元素具有相對應的DOM事件,那麼能夠使用 $listeners 獲取父組件傳遞進來的全部事件函數,再經過v-on="xxx"綁定到相對應的內部元素上便可。 簡單的來講,就是父組件向子組件傳遞的全部方法都存在在$listeners

有時候咱們會使用 .native 修飾符把原生事件綁定到組件上,可是這樣存在弊端,若是組件的根元素不能使用 某事件時,這個綁定就會失效,並且還不容易控制它的事件範圍,因此咱們通常不用這個修飾符

示例:

//父組件  
<template>
  <div>
    ParentPage
    <button @click="handleClick">ParentClick</button>
    <Child @customClick="handleClick" />
  </div>
</template>

<script>
import Child from "./Child";
export default {
  name: "ParentPage",

  components: {
    Child
  },

  methods: {
    handleClick() {
      alert("hello");
    }
  }
};
</script>
複製代碼
//子組件
<template>
  <div>
    ChildPage
    <!-- <button @click="$emit('customClick')">ChildClick</button> -->
    <button @click="$listeners.customClick">ChildClick</button>
  </div>
</template>

<script>
import SubChild from "./SubChild.vue";
export default {
  name: "ChildPage",
  components: {
    SubChild
  },
  data() {
    return {};
  }
};
</script>
複製代碼

當多層組件引用時,子組件傳遞父組件方法 v-on="$listeners" 至子子組件

// 子組件
<template>
  <div>
    ChildPage
    <!-- <button @click="$emit('customClick')">ChildClick</button> -->
    <button @click="$listeners.customClick">ChildClick</button>
    <SubChild v-on="$listeners" />
  </div>
</template>

<script>
import SubChild from "./SubChild.vue";
export default {
  name: "ChildPage",
  components: {
    SubChild
  },
  data() {
    return {};
  }
};
</script>
複製代碼
// 子子組件
<template>
  <div>
    SubChildPage
    <button @click="$listeners.customClick">SubChildClick</button>
  </div>
</template>

<script>
export default {
  name: "SubChildPage",
  data() {
    return {};
  }
};
</script>
複製代碼

相信若是好好看了以上的代碼,你就會理解的

$emit

1.父組件可使用props把數據傳給子組件
2.子組件可使用 $emit 觸發父組件的自定義事件

vm.$emit( event, arg ) //觸發當前實例上的事件
vm.$on( event, fn );//監聽event事件後運行 fn;
複製代碼

示例:

//父組件
<template>
  <div>
    <div>$emit子組件調用父組件的方法並傳遞數據</div>
    <h1>父組件數據:{{msg}}</h1>
    <emit-ch @updateInfo="updateInfo" :sendData="msg"></emit-ch>
  </div>
</template>
<script>
import emitCh from "./$emitCh";
export default {
  name: "emitFa",
  components: { emitCh },
  data() {
    return {
      msg: "北京"
    };
  },
  methods: {
    updateInfo(data) {
      // 點擊子組件按鈕時觸發事件
      console.log(data);
      this.msg = data.city; // 改變了父組件的值
    }
  }
};
</script>
複製代碼
<template>
  <div class="train-city">
    <h3>父組件傳給子組件的數據:{{sendData}}</h3>
    <br />
    <button @click="select()">點擊子組件</button>
  </div>
</template>

<script>
export default {
  name: "emitCh", // 至關於一個全局 ID,能夠不寫,寫了能夠提供更好的調試信息
  props: ["sendData"], // 用來接收父組件傳給子組件的數據
  data() {
    return {};
  },
  computed: {},
  methods: {
    select() {
      let data = {
        city: "杭州"
      };
      this.$emit("updateInfo", data); // select事件觸發後,自動觸發updateInfo事件
    }
  }
};
</script>
複製代碼

整體來講,就是用子組件觸發父組件裏的方法,子組件裏面this.$emit裏的this,就指的是父組件

$refs

$refs 的使用方法就是在元素或組件標籤上添加ref屬性指定一個引用信息,引用信息將會註冊在父組件的$refs對象上,在js中使用$refs來指向DOM元素或組件實例;

  • 首先給你的子組件作標記:
    <firstchild ref="test"></firstchild>

  • 而後在父組件中,經過 this.$refs.test 就能夠訪問這個子組件,包括訪問子組件裏的 data 裏的數據,而且還能夠調用它的函數

$parent$children

說了上面的 $refs 接下來講說 $parent$children

  • 使用 this.$parent 能夠查找當前組件的父組件。
  • 使用 this.$children 能夠查找當前組件的直接子組件,能夠遍歷所有子組件, 須要注意 $children 並不保證順序,也不是響應式的。

固然你也可使用 this.$root 來查找根組件,並能夠配合$children遍歷所有組件。

注:這兩個都是不限制距離的,就是說能夠直接查找到最外層數據或者最內層數據,固然,若是你能很清楚的知道子組件的順序,你也能夠用下標來操做

示例:

//父組件
<template>
  <div class="game">
    <h2>{{ msg }}</h2>
    <LOL ref="lol"></LOL>
    <DNF ref="dnf"></DNF>
  </div>
</template>
<script>
import LOL from "@/components/game/LOL";
import DNF from "@/components/game/DNF";
export default {
  name: "game",
  components: {
    LOL,
    DNF
  },
  data() {
    return {
      msg: "Game",
      lolMsg: "Game->LOL",
      dnfMsg: "Game->DNF"
    };
  },
  methods: {},
  mounted() {
    //注意 mounted

    //讀取子組件數據,注意$children子組件的排序是不安全的
    console.log(this.$children[0].gameMsg); //LOL->Game

    //讀取命名子組件數據
    console.log(this.$refs.dnf.gameMsg); //DNF->Game

    //從根組件查找組件數據
    console.log(this.$root.$children[0].msg); //APP
    console.log(this.$root.$children[0].$children[0].msg); //Game
    console.log(this.$root.$children[0].$children[0].$children[0].msg); //Game->LOL
    console.log(this.$root.$children[0].$children[0].$children[1].msg); //Game->DNF
  }
};
</script>
複製代碼
//子組件LOL  
<template>
  <div class="lol">
    <h2>{{ msg }}</h2>
  </div>
</template>

<script>
export default {
  name: "LOL",
  data() {
    return {
      msg: "LOL",
      gameMsg: "LOL->Game"
    };
  },
  methods: {},
  created() {
    //直接讀取父組件數據
    this.msg = this.$parent.lolMsg;
  }
};
</script>
複製代碼
//子組件DNF
<template>
  <div class="dnf">
    <h2>{{ msg }}</h2>
  </div>
</template>

<script>
import Bus from "../../utils/bus.js";
export default {
  name: "DNF",
  data() {
    return {
      msg: "DNF",
      gameMsg: "DNF->Game"
    };
  },
  methods: {},
  created() {
    //從根組件向下查找父組件數據
    this.msg = this.$root.$children[0].$children[0].dnfMsg;
    //this.msg = this.$children.dnfMsg;
  }
};
</script>
複製代碼

上面的有些是使用下標的,固然也能夠不使用下標,直接 $parent 獲取父組件的實例 $children獲取全部的子組件

provideinject

provideinject使用場景也是組件傳值,尤爲是祖父組件--子子組件等有跨度的組件間傳值,單向傳值(由provide的組件傳遞給inject的組件)。 不推薦使用

  • provide 選項應該是一個對象或返回一個對象的函數。該對象包含可注入其子孫的屬性。
  • inject 一般是一個字符串數組。

示例:

//父組件
<template>
  <div>
    <father-dom>
    </father-dom>
  </div>
</template>
<script>
import sonDom from "./sonDom.vue";
export default {
  provide: {
    fooNew: "bar"
  },
  data() {
    return {};
  },
  components: { sonDom },
  methods: {}
};
</script>
複製代碼
//子組件
<template>
  <div>
    <child-dom></child-dom>
  </div>
</template>
<script>
import childDom from "./childDom.vue";
export default {
  name: "son-dom",
  components: { childDom }
};
</script>
複製代碼
//子子組件  
<template>
  <div>
    <p>fooNew:{{fooNew}}</p>
  </div>
</template>
<script>
export default {
  name: "childDom",
  inject: ["fooNew"],
  methods: {}
};
</script>
複製代碼

Vuex

接下來講一下咱們的壓軸好戲,最 6 的一種數據傳遞方式:Vuex

當咱們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞。

  • 多個視圖依賴於同一狀態。
  • 來自不一樣視圖的行爲須要變動同一狀態。來自不一樣視圖的行爲須要變動同一狀態。

對於問題一,傳參的方法對於多層嵌套的組件將會很是繁瑣,而且對於兄弟組件間的狀態傳遞無能爲力。對於問題二,咱們常常會採用父子組件直接引用或者經過事件來變動和同步狀態的多份拷貝。以上的這些模式很是脆弱,一般會致使沒法維護的代碼。

所以,咱們爲何不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,咱們的組件樹構成了一個巨大的「視圖」,無論在樹的哪一個位置,任何組件都能獲取狀態或者觸發行爲!

另外,經過定義和隔離狀態管理中的各類概念並強制遵照必定的規則,咱們的代碼將會變得更結構化且易維護。

Vuex

  • state
    Vuex裏的state至關於一個全局的state,你能夠在component的任何地方獲取和修改它。使用 Vuex 並不意味着你須要將全部的狀態放入 Vuex。雖然將全部的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。若是有些狀態嚴格屬於單個組件,最好仍是做爲組件的局部狀態。你應該根據你的應用開發須要進行權衡和肯定。
//獲取state
this.$store.state.count

//vuex的輔助方法
import { mapState } from 'vuex'
computed:mapState([
  'count'
])
複製代碼
  • getters
    Vuex裏的getters相似於computed,有時候咱們須要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數,若是有多個組件須要用到此屬性,咱們要麼複製這個函數,或者抽取到一個共享函數而後在多處導入它——不管哪一種方式都不是很理想。
    Vuex 容許咱們在 store 中定義「getter」(能夠認爲是 store 的計算屬性)。就像計算屬性同樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。
//直接使用
this.$store.getters.doneTodosCount

//使用輔助方法
import { mapGetters } from 'vuex'
computed:mapGetters({
  doneCount: 'doneTodosCount'
})
複製代碼
  • mutations
    更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。使用常量替代 mutation 事件類型在各類 Flux 實現中是很常見的模式。這樣可使 linter 之類的工具發揮做用,同時把這些常量放在單獨的文件中可讓你的代碼合做者對整個 app 包含的 mutation 一目瞭然。用不用常量取決於你——在須要多人協做的大型項目中,這會頗有幫助。但若是你不喜歡,你徹底能夠不這樣作。
    一條重要的原則就是要記住 mutation 必須是同步函數,mutation 中的異步函數中的回調,當 mutation 觸發的時候,回調函數尚未被調用,devtools 不知道何時回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。(一般請求和計時器方法都是異步函數)
//觸發mutations
this.$store.commit('xxx')

//輔助函數
import { mapMutations } from 'vuex'
methods:mapMutations(['increment' ])
複製代碼
  • actions
    對於Action的官方解釋以下。
    相似於 mutation,不一樣在於:
  • Action 提交的是 mutation,而不是直接變動狀態。
  • Action 能夠包含任意異步操做。

我的理解以下:
若是有異步的複雜邏輯而且能夠重複調用就使用Action。

//觸發action
store.dispatch('increment')

//輔助函數
import { mapActions } from 'vuex'
methods:mapActions(['increment' ])
複製代碼
  • Module
    因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。

爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})
複製代碼

相信看了本篇文章,你確定會對組件之間的數據傳遞和通訊有了更深的理解


^_<

相關文章
相關標籤/搜索