Vue整理——組件間的通訊

經過閱讀了官方文檔和大量網友的博文,融入本身的想法,整理出對組件通訊的見解,第一次發文,若有不周敬請諒解哦~javascript

進行組件間通訊的緣由

組件實例的做用域是孤立的,但組件間的聯繫和交互不可避免。當組件間進行數據交互時,組件通訊必不可少。
html

組件通訊的類型

因而組件的通訊能夠分爲兩種狀況:vue

父子組件間通訊(父傳子,子傳父),非父子組件通訊。java



父傳子類型


prop傳值


step1:父組件用prop自定義屬性傳遞屬性,prop有字面量語法和動態語法。字面量只能將數據按字符串的形式傳遞;動態語法相似於v-bind,能夠將父組件數據的實時變化傳遞給子組件。
vuex




step2:子組件須要用props來顯式地聲明prop。子組件props的寫法有兩種:
數組

  • 數組形式:


  • 對象形式:能夠進行條件約束


子傳父

1.回調傳參

  1. 父將函數用prop傳遞給子,子在調用這個函數時,將數據做爲參數傳遞給父組件app

    父組件中:

    <child2 :changeParent="changeMyself"></child2>複製代碼

    methods: {
        // 父組件改變自身數據的方法
        changeMyself (chidData) {
            this.parentChangeData = chidData
        }
    }複製代碼

    子組件中:  

    export default {
      // 子組件顯示聲明props父組件的函數
      props: {
        changeParent: {
          requried: true, // 表明是否爲必傳
          type: Function // 表明數據類型
        }
      },
      data () {
        return {
          childData: '我要把數據傳給父'
        }
      }
    }複製代碼

    <button @click="changeParent(childData)">點我傳遞數據給父組件</button>複製代碼

2.自定義事件+事件監聽

子$emit自定義一個事件,第一個參數爲事件名,第二個參數爲數據。父v-on綁定一個事件監聽器,在綁定的method中以參數形式得到子組件的數據。異步

子組件中:
函數

methods: {
    // 自定義事件$emit暴露數據,數據做爲參數
    changeParent () {
      this.$emit('change', this.childData)
    }
}複製代碼

父組件中:
組件化

<!-- 父組件綁定監聽,獲得子組件數據--> <child3 @change="changeMyself"></child3>複製代碼

methods: {
    // 父組件改變自身數據的方法
    changeMyself (chidData) {
        this.parentChangeData = chidData
    }
}複製代碼

3.直接訪問

用ref對子組件進行標記,父組件直接經過this.$refs[子組件ref].[子組件屬性/方法]來得到子組件的數據。

注意:"$refs 只在組件渲染完成後才填充,而且它是非響應式的。它僅僅做爲一個直接訪問子組件的應急方案——應當避免在模版或計算屬性中使用 $refs 。" 官網這麼說的,只是應急用,通常不推薦這樣使用

思考:單項數據流

Prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,可是反過來不會。這是爲了防止子組件無心間修改了父組件的狀態,來避免應用的數據流變得難以理解。

另外,每次父組件更新時,子組件的全部 prop 都會更新爲最新值。這意味着你不該該在子組件內部改變 prop。若是你這麼作了,Vue 會在控制檯給出警告。

在兩種狀況下,咱們很容易忍不住想去修改 prop 中數據:

  1. Prop 做爲初始值傳入後,子組件想把它看成局部數據來用;

  2. Prop 做爲原始數據傳入,由子組件處理成其它數據輸出。

對這兩種狀況,正確的應對方式是:

  1. 定義一個局部變量,並用 prop 的值初始化它:


  2. 定義一個計算屬性,處理 prop 的值並返回:


思考:子與父的雙向綁定

單項數據流致使子組件不能直接去改動父組件的數據。但真實場景中,有不少時候須要在子組件中更改父組件的數據。

方法:

  1. 父有一個改變其自身數據的方法,用prop傳給子,子在須要改變父組件數據的時候調用該方法。(與回調參數方法一致)

  2. 子組件經過事件發送,$emit事件將數據傳給父組件,父組件監聽後本身執行改變自身數據的方法。(與子傳父的第二種方法一致)

  3. 本身想到的方法:父組件有一個改變自身數據的方法,子經過this.$parent.[父組件方法]直接調用父組件方法,將子組件的數據做爲參數傳遞給父組件從而改變父組件數據

    1. 父組件中:

      methods: {
          // 父組件改變自身數據的方法
          changeMyself (chidData) {
              this.parentChangeData = chidData
          }
      }複製代碼

      子組件中:

      export default {
        data () {
          return {
            childData: '我是子要傳給父的數據'
          }
        },
        methods: {
          change () {
            // 經過直接訪問的方法調用
            this.$parent.changeMyself(this.childData)
          }
        }
      }複製代碼

    但以上的方法都是基於事件觸發的,不能保證子父組件的數據時刻同步

    4.使用.sync綁定修飾符在父組件prop中顯示強調雙向綁定。子組件在watch中使用$emit(‘update:data’,newVal)監聽,更新父組件prop傳過來的數據,父組件不須要再監聽該update方法。


    父組件中:

    <!-- 顯式綁定.sync修飾符 --> <child6 :parentData.sync= "parentData"></child6>複製代碼

    子組件中:

    props: ['parentData']複製代碼

    // 子組件進行數據監聽
    watch: {
        childData (newVal, oldVal) {
          this.$emit('update:parentData', newVal)
        }
    }複製代碼

這裏的自定義事件與子傳父的自定義事件有一些不一樣。

<child6 :parentData.sync="parentData"></child6>

會被擴展爲:

<child6 :parentData"parentData" @update:parentData="val => parentData = val"></child6>

思考:

自定義事件發生時候運行的響應表達式是<child6 :parentData="parentData" @update:parentData="val => parentData = val"></child6>中的 "val => bar = val"

在子傳父的「經過$emit事件從子組件向父組件中傳遞數據」 裏,自定義事件發生時候運行的響應表達式是:<child @chang="changeMyself"></child>中的changeMyself

對前者, 表達式 val => bar = val意味着強制讓父組件的數據等於子組件傳遞過來的數據, 這個時候,咱們發現父子組件的地位是平等的。 父能夠改變子(數據), 子也能夠改變父(數據)。

對後者, changeMyself是在父組件中定義的, 在這個函數裏, 能夠對從子組件接受來的數據作任意的操做或處理, 決定權徹底落在父組件中, 也就是: 父能夠改變子(數據), 但子不能直接改變父(數據)!, 父中數據的變更只能由它本身決定。

有一個投機取巧的辦法:在父用prop傳數據給子時,若子組件中props的類型爲對象或數組時,能夠直接在子組件中修改這個prop來的數據,且不會被vue檢測報錯,但這樣會使得數據流變得更加難以分析,同時,當props的類型爲引用數據類型時,要注意在子組件中對對象進行深拷貝,防止隱性修改父組件的對象。


兄弟傳兄弟(非父子)

1.兩個兄弟組件有共同的父組件

父組件將數據prop給一個兄弟A,將改變數據的方法prop給另外一個兄弟B,B調用方法改變A的數據,將數據和改變數據提高到了父組件內部,而後再分發下去。

父組件中:

<!-- 父組件把改變數據的方法傳給child2,把數據傳給child,將數據通訊提高到父組件層次 --> <child2 :changeParent="changeMyself"></child2> <child :parent="parentChangeData"></child>複製代碼

child2中:

<!-- 子組件調用方法,將數據做爲參數 --> <button @click="changeParent(childData)">點我傳遞數據給父組件</button>複製代碼

export default {
  // 子組件顯示聲明props父組件的函數
  props: {
    changeParent: {
      requried: true, // 表明是否爲必傳
      type: Function // 表明數據類型
    }
  },
  data () {
    return {
      childData: '我要把數據傳給父'
    }
  }
}複製代碼

child中:

// 顯示聲明props父組件的數據
props: {
    parent: {
      requried: true, // 表明是否爲必傳
      type: String, // 表明數據類型
      default: '我是默認值'
    }
}複製代碼

2.路由傳參

把須要跨頁面傳遞的數據放到url後面,跳轉到另外頁面時直接獲取url字符串獲取想要的參數便可。

{
    path: '/params/:id'
    name: '',
    component: Sample
}複製代碼

<router-link :to="params/12">跳轉路由</router-link>複製代碼

在跳轉後的組件中用$route.params.id去獲取到這個id參數爲12,但這種只適合傳遞比較小的數據,數字之類的。

3.EventBus

首先建立一箇中央時間總線,在須要使用的組件裏引入。組件A用this.Bus.$emit('eventName', value)觸發事件,組件B用this.Bus.$on('eventName', value => { this.print(value) })接收事件。在$emit以前,必須已經$on, 所以廣泛採用在created鉤子中進行$on.


// 新建Bus.js文件
import Vue from 'vue'
export default new Vue()複製代碼

組件A傳送數據:

<script>
// 在須要使用的組件中引用
import Bus from '@/components/Bus'
export default {
  data () {
    return {
      childData: '我是兄弟A,把個人數據放進eventBus'
    }
  },
  methods: {
    submit () {
      // 觸發事件
      Bus.$emit('change', this.childData)
    }
  }
}
</script>複製代碼

組件B接收數據:

get () {
  // 監聽接收事件
  Bus.$on('change', value => {
    this.myData = value
  })
}複製代碼

// 組件銷燬時,解除綁定
destroyed () {
    Bus.$off('change')
}複製代碼


組件間的通訊均可以用EventBus實現, 不管有多少個組件,只要保證eventName不同就能夠了,專門設置一個空的Bus實例來做爲中央事件總線,而不是直接訪問root,更清晰也更利於管理。

特殊的eventBus

傳統的eventBus只負責$emit和$on,與數據沒有任何的交集。這使得數據不是「長效」的,只在$emit後生效,而且同一組件屢次生成會屢次$on,路由切換時,還要考慮新組件的綁定和舊組件的解除綁定。

方法:考慮將$on放在Bus中完成,修改Bus爲:

// 新建Bus.js文件
// Bus進行監聽,將數據直接放在Bus中
import Vue from 'vue'
const Bus = new Vue({
  data () {
    return {
      child7Val: ''
    }
  },
  created () {
    this.$on('change', value => {
      this.child7Val = value
    })
  }
})
export default Bus複製代碼


發出數據的組件不變

<script>
// 在須要使用的組件中引用
import Bus from '@/components/Bus'
export default {
  data () {
    return {
      childData: '我是兄弟A,把個人數據放進eventBus'
    }
  },
  methods: {
    submit () {
      // 觸發事件
      Bus.$emit('change', this.childData)
    }
  }
}
</script>複製代碼


接收數據的組件修改成用計算屬性直接從Bus中存取數據,使用計算屬性是爲了保證數據的動態性。

computed: {
    child7Val () {
      return Bus.child7Val
    }
 }複製代碼


Vuex

個人理解是,Vuex至關於一個大型的專門用來儲存共享變量的倉庫。

在安裝Vuex以後,建立store.js文件

import Vue from 'vue'
import Vuex from 'vuex'

import app from './modules/app'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    // 存放共享變量
    msg: '我是原始數據'
  },
  getter: {
    // 至關於store中的計算屬性
    mymsg: state => {
      return state.msg
    }
  },
  mutations: {
    // 修改state,vue推薦使用大寫
    MUTATIONSMSG (state, payload) {
      state.msg = payload.msg
    }
  },
  actions: {
    // 與mutations相似,支持異步
    mutationsMsg (context, payload) {
      context.commit('MUTATIONSMSG', payload)
    }
  },
  modules: {
    app
  },
  strict: process.env.NODE_ENV !== 'production'
})

export default store複製代碼

當使用mutations來修改state時:

組件A使用store中的數據:

<template> <div class="child"> <h3>組件A</h3> {{$store.state.msg}} </div> </template>複製代碼

組件B修改store中的數據:

<script>
export default {
  data () {
    return {
      myData: '組件B的數據'
    }
  },
  methods: {
    get () {
      this.$store.commit('MUTATIONSMSG', this.myData)
    }
  }
}
</script>複製代碼

當使用actions修改state時:

<script>
export default {
  data () {
    return {
      myData: '組件B的數據'
    }
  },
  methods: {
    get () {
      this.$store.dispatch('mutationsMsg', this.myData)
    }
  }
}
</script>複製代碼

state用來存放共享變量,經過this.$store.state.[變量]來獲取共享變量的值。

getter,能夠增長一個getter派生狀態,(至關於store中的計算屬性),store.getters.方法名()用來得到共享變量的值。

mutations用來存放修改state的方法(至關於set)。

actions也是用來存放修改state的方法,不過action是在mutations的基礎上進行。在actions先commit mutations裏的方法,再由使用actions的組件進行dispatch


思考

  • 組件化的思想就是但願組件的獨立性,數據能互不干擾

  • 經過組件A直接去修改組件B的值,好比雙向綁定,雖然方便,但增長了組件間的耦合性。最好就是若是組件A要修改組件B的值,那麼就將修改的數據暴露出去,由B獲得數據後,自行修改。

相關文章
相關標籤/搜索