vue中父子組件通訊

通訊方式:

> props(經常使用)
> props和$emit(經常使用)
> .sync(語法糖)
> model(單選框和複選框場景可使用)
> $attr$listeners(組件封裝用的比較多)
> provide和inject(高階組件/組件庫使用比較多)
> eventBus(小項目中使用就好)
> Vuex(中大型項目推薦使用)
> $parent$children(推薦少用)
> $root(組件樹的根,用的少)
> 其餘通訊方式
複製代碼

1、props

當前組件接收到的 props 對象。Vue 實例代理了對其 props 對象屬性的訪問。javascript

在使用prop傳參時須要注意:vue

  • vue的設計理念是單向數據流,不建議在子組件直接更改父級的數據。
  • 未在父組件data中聲明的對象屬性,子組件沒法獲取更新內容。
  • 數組的變化和更新,取決於vue重寫數組方法是否有實現數據監聽功能。vue有兩種觀察數組的方法:變異方法(push、pop、shift、unshift、splice、sort、reverse)和非變異方法(filter、concat、slice),變異方法能夠修改原數組,非變異方法不能夠修改原數組,可是非變異方法能夠用新數組替換舊數組來實現數據的從新渲染。
// Father組件
    <template>
      <div class="father">
        <Child 
          :msg='msg' 
          :changeMsg='handleChangeMsg'
          />
      </div>
    </template>
    
    <script>
    import Child from './Child.vue'
    
    export default {
        name: 'father',
        data() {
            return {
                msg: 'msg'
            }
        },
        methods: {
            handleChangeMsg(value) {
                this.msg = value
            }
        },
        components: {
            Child
        }
    }
    </script>
複製代碼
// Child組件
    <template>
      <div class="child">
        <h3>String使用:</h3>
        <div>
          {{msg}}
        </div>
        <button @click="handleChangeMsg">修改父組件的msg</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'Child',
      props: {
        msg: {
          type: String,
          default: ''
        },
        changeMsg: {
          type: Function,
          default: () => {}
        }
      },
      methods: {
          handleChangeMsg() {
            // this.msg = 'a' // 控制檯會報錯
            
            // 可使用父組件給的方法來改數據
            this.changeMsg('hello world')
          }
      }
    }
    </script>
複製代碼

2、props和$emit

觸發當前實例上的事件。附加參數都會傳給監聽器回調。java

emit的使用場景主要是在子組件要傳參數給父組件,經過$emit來觸發父組件給的監聽器。git

// Father組件
    <template>
      <div class="father">
        {{value}}
        <Child v-on:change="handleChange" :value='value' />
      </div>
    </template>
    
    <script>
    import Child from './Child.vue'
    
    export default {
        name: 'father',
        data() {
            return {
                value: ''
            }
        },
        methods: {
            handleChange(value) {
              this.value = value
            }
        },
        components: {
            Child
        }
    }
    </script>
複製代碼
// Child組件
    <template>
        <div class="child">
            <input type="text" :value="value" @change="_change">
        </div>
    </template>
    
    <script>
    export default {
        name: 'Child',
        props: {
            value: String
        },
        methods: {
            _change(e) {
                this.$emit('change', e.target.value)
            }
        }
    }
    </script>
複製代碼

3、.sync語法糖(2.3.0+ 新增)

在有些狀況下,咱們可能須要對一個 prop 進行「雙向綁定」。不幸的是,真正的雙向綁定會帶來維護上的問題,由於子組件能夠修改父組件,且在父組件和子組件都沒有明顯的改動來源。所以以 update:myPropName 的模式觸發事件取而代之。github

  • 注意帶有 .sync 修飾符的 v-bind 不能和表達式一塊兒使用 (例如 v-bind:title.sync=」doc.title + ‘!’」 是無效的)。取而代之的是,你只能提供你想要綁定的屬性名,相似 v-model。
  • 將 v-bind.sync 用在一個字面量的對象上,例如 v-bind.sync=」{ title: doc.title }」,是沒法正常工做的,由於在解析一個像這樣的複雜表達式的時候,有不少邊緣狀況須要考慮。
  • 當咱們用一個對象同時設置多個 prop 的時候,也能夠將這個 .sync 修飾符和 v-bind 配合使用<Child :value.sync='value' v-bind.sync='obj' />,這樣會把 obj 對象中的每個屬性 (如 title) 都做爲一個獨立的 prop 傳進去,而後各自添加用於更新的 v-on 監聽器。
// Father組件
    <template>
      <div class="father">
        {{value}}
        <br/>
        {{obj}}
        <br/>
        <!-- <Child v-on:update:value='value = $event' /> -->
        <!-- sync是上面的語法糖 -->
        <!-- <Child :value.sync='value' /> -->
        <Child :value.sync='value' v-bind.sync='obj' />
      </div>
    </template>
    
    <script>
    import Child from './Child.vue'
    
    export default {
        name: 'father',
        data() {
            return {
                value: 'hello',
                obj: {
                  title: '主題',
                  content: '文本'
                }
            }
        },
        components: {
            Child
        }
    }
    </script>
複製代碼
// Child組件
    <template>
        <div class="child">
            <input type="text" :value="value" @change="_change">
            <br/>
            <button @click="_changeObj">改變obj對象</button>
        </div>
    </template>
    
    <script>
    export default {
        name: 'Child',
        props: {
            value: String
        },
        methods: {
            _change(e) {
                this.$emit('update:value', e.target.value)
            },
            _changeObj() {
                this.$emit('update:title', '新主題')
            }
        }
    }
    </script>
複製代碼

4、model(2.2.0 新增)

容許一個自定義組件在使用 v-model 時定製 prop 和 event。默認狀況下,一個組件上的 v-model 會把 value 用做 prop 且把 input 用做 event,可是一些輸入類型好比單選框和複選框按鈕可能想使用 value prop 來達到不一樣的目的。使用 model 選項能夠迴避這些狀況產生的衝突。element-ui

// Father組件
    <template>
      <div class="father">
        輸入的值是:{{phoneInfo}}
        <Child v-model="phoneInfo" />
      </div>
    </template>
    
    <script>
    import Child from './Child.vue'
    
    export default {
      name: 'father',
      data() {
        return {
          phoneInfo: {
            areaCode: '+86',
            phone: ''
          }
        }
      },
      components: {
        Child
      }
    }
    </script>
    
複製代碼
// Child組件
    <template>
        <div class="child">
            <select
                :value="phoneInfo.areaCode"
                placeholder="區號"
                @change="_changeAreaCode"
                >
                <option value="+86">+86</option>
                <option value="+60">+60</option>
            </select>
            <input
                :value="phoneInfo.phone"
                type="number"
                placeholder="手機號"
                @input="_changePhone"
            />
        </div>
    </template>
    
    <script>
    export default {
        name: 'Child',
        model: {
            prop: 'phoneInfo', // 默認 value
            event: 'change' // 默認 input
        },
        props: {
            phoneInfo: Object
        },
        methods: {
            _changeAreaCode(e) {
                this.$emit('change', {
                    ...this.phoneInfo,
                    areaCode: e.target.value
                })
            },
            _changePhone(e) {
                this.$emit('change', {
                    ...this.phoneInfo,
                    phone: e.target.value
                })
            }
        }
    }
    </script>
複製代碼

5、$attrs和$listeners (2.4.0 新增)

  1. $attrs包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件——在建立高級別的組件時很是有用。
  2. $listeners包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件——在建立更高層次的組件時很是有用。
  • inheritAttrs也是2.4.0 新增,默認狀況下父做用域的不被認做 props 的特性綁定 (attribute bindings) 將會「回退」且做爲普通的 HTML 特性應用在子組件的根元素上。當撰寫包裹一個目標元素或另外一個組件的組件時,這可能不會老是符合預期行爲。經過設置 inheritAttrs 到 false,這些默認行爲將會被去掉。而經過 實例屬性 $attrs 可讓這些特性生效,且能夠經過 v-bind 顯性的綁定到非根元素上。
// 第一個組件
    <template>
      <div class="one">
        第一個組件的value:{{value}}
        <Two :value='value' @change="handleChange" @changeTwo.native='handleChange' :test='test' />
      </div>
    </template>
    
    <script>
    import Two from './Two.vue'
    
    export default {
        name: 'one',
        data() {
            return {
                value: 10,
                test: 'hello'
            }
        },
        methods: {
            handleChange(value, msg) {
              this.value = value
              console.log(msg)
            }
        },
        components: {
            Two
        }
    }
    </script>
複製代碼
// 第二個組件
    <template>
        <div class="two">
            <button @click="_change">第二個組件</button>
            <br/>
            第二個組件的value:{{$attrs.value}}
            <Three v-bind="$attrs" v-on="$listeners"/>
        </div>
    </template>
    
    <script>
    import Three from './Three.vue'
    
    export default {
        inheritAttrs: false, // 
        name: 'two',
        props: {
            test: String
        },
        created() {
            console.log('-----第二個組件-----')
            console.log(this.$attrs) // 獲取父級做用域中綁定在該組件上且沒有在Prop聲明的屬性
            // {value: 10}
            console.log(this.$listeners) // 獲取父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器
            // {change: ƒ}
        },
        methods: {
            _change() {
                this.$emit('change', 2, '來自第二個組件的事件觸發')
            }
        },
        components: {
            Three
        }
    }
    </script>
複製代碼
// 第三個組件
    <template>
      <div class="three">
          <button @click="_change">第三個組件</button>
          <br/>
          第三個組件中顯示第一個組件的value:{{$attrs.value}}
      </div>
    </template>
    
    <script>
    export default {
        name: 'three',
        created() {
            console.log('-----第三個組件-----')
            console.log(this.$attrs)
            console.log(this.$listeners)
        },
        methods: {
            _change() {
                this.$emit('change', 3, '來自第三個組件的事件觸發,感謝$listeners')
            }
        }
    }
    </script>
複製代碼

6、provide / inject(2.2.0 新增)

這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。數組

  • provide 和 inject 主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中
  • provide 選項應該是一個對象或返回一個對象的函數。該對象包含可注入其子孫的屬性。在該對象中你可使用 ES2015 Symbols 做爲 key,可是隻在原生支持 Symbol 和 Reflect.ownKeys 的環境下可工做。
  • inject 選項應該是一個字符串數組,或一個對象,對象的 key 是本地的綁定名。value 是在可用的注入內容中搜索用的 key (字符串或 Symbol),或一個對象。該對象的:from 屬性是在可用的注入內容中搜索用的 key (字符串或 Symbol),default 屬性是降級狀況下使用的 value。
  • provide 和 inject 綁定並非可響應的。這是刻意爲之的。然而,若是你傳入了一個可監聽的對象,那麼其對象的屬性仍是可響應的。
// one組件
    <template>
      <div class="one"> data中的b:{{b}} <br> 可響應對象test:{{test}} <br> <button @click="_change">第一個組件的按鈕</button> <Two/> </div> </template> <script> import Vue from "vue" import Two from './Two.vue' import symbol from './symbol' export default { // provide: { // a: 'A' // }, provide () { this.test = Vue.observable({ // 可響應對象的建立,建議傳響應式對象 count: 0 }) return { a: 'A', test: this.test, // 賦與對象指針 b: this.b, // 賦值操做 [symbol.KEY]: 'C', one: this, onChange: this.handleChange } }, name: 'one', data () { return { b: 'B' } }, created () { console.log('-----第一個組件-----') console.log('data中b=' + this.b) }, methods: { handleChange (value, msg) { this.b = value console.log(msg) }, _change () { this.b = 'one....b' this.test.count++ } }, components: { Two } } </script> 複製代碼
// two組件
    <template>
      <div class="two"> inject中b的值:{{b}} <br> inject中test的值:{{test}} <br> <button @click="onChange">第二個組件的按鈕</button> <Three/> </div> </template> <script> import Three from './Three.vue' import symbol from './symbol' export default { // inject: ['a', 'b'], inject: { a: { default: 'AA' // 在 2.5.0+ 的注入能夠經過設置默認值使其變成可選項 }, b: { from: 'b', // 若是它須要從一個不一樣名字的屬性注入,則使用 from 來表示其源屬性 default: 'no value!' }, key: { from: symbol.KEY, default: () => ['no', 'value'] // 與 prop 的默認值相似,你須要對非原始值使用一個工廠方法 }, one: { default: () => ({}) }, _change: { // 命名與子組件衝突能夠更改別名 from: 'onChange' }, test: { from: 'test' } }, name: 'two', props: { two_p_b: { default () { return this.b } } }, data () { return { two_d_b: this.b } }, created () { console.log('-----第二個組件-----') console.log('inject注入的a=' + this.a) console.log('inject注入的b=' + this.b) console.log('-------------------') console.log('inject注入整個one組件') console.log(this.one) console.log('-------------------') console.log('props中b=' + this.two_p_b) console.log('data中b=' + this.two_d_b) console.log(`inject注入的Symbol類型的key=${JSON.stringify(this.key)}`) }, methods: { onChange () { if (this.one && this.one.handleChange) { // this._change('two', '來自第二個組件的觸發') this.test.count++ // 由於test指向的是個響應式對象,因此能夠這麼使用 this.one.b = 'two.....b' // this.one.handleChange('two', '來自第二個組件的觸發') } } }, components: { Three } } </script> 複製代碼
// three組件
    <template>
      <div class="three">
        inject中b的值:{{b}}
        <br>
        計算屬性中獲取b的值:{{getB}}
        <br>
        inject中test的值:{{test}}
        <br>
        計算屬性中獲取test的值:{{getTest}}
        <br>
        <button @click="_change">第三個組件的按鈕</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'three',
      inject: ['onChange', 'one', 'test', 'b'], // 其他和two組件中的使用基本一致,這裏不一一列舉了
      created () {
        console.log('-----第三個組件-----')
        console.log('inject注入整個one組件')
        console.log(this.one)
      },
      computed: {
        getTest () {
          return this.test
        },
        getB () {
          return this.b
        }
      },
      methods: {
        _change () {
          if (this.one && this.one.handleChange) {
            this.onChange('three.....b', '來自第三個組件的觸發')
            this.test.count++
            // this.one.handleChange('three', '來自第三個組件的觸發')
          }
        }
      }
    }
    </script>
複製代碼
// symbol.js文件的內容
    const KEY = Symbol()
    
    export default {
        KEY
    }
複製代碼

7、$parent

指定已建立的實例之父實例,在二者之間創建父子關係。子實例能夠用 this.$parent 訪問父實例,子實例被推入父實例的 $children 數組中。bash

  • 節制地使用 $parent 和 $children - 它們的主要目的是做爲訪問組件的應急方法。更推薦用 props 和 events 實現父子組件通訊。
// Father組件
    <template>
      <div id="father">
        <Child/>
      </div>
    </template>
    
    <script>
    import Child from './Child.vue'
    
    export default {
      name: 'father',
      data () {
        return {
          msg: 'hello'
        }
      },
      created () {
        this.$nextTick(() => {
          console.log(this.$children)
        })
      },
      components: {
        Child
      }
    }
    </script>
複製代碼
// Child組件
    <template>
      <div class="child"> 父組件的值:{{$parent.msg}} <br> <input type="text" @change="change"> </div> </template> <script> export default { name: 'Child', created () { console.log(this.$parent) }, methods: { change(e) { this.$parent.msg = e.target.value } } } </script> 複製代碼

8、EventBus

聲明一個全局Vue實例變量 EventBus , 把全部的通訊數據,事件監聽都存儲到這個變量上。這樣就達到在組件間數據共享了,有點相似於 Vuex。app

  • 這種方式只適用於極小的項目,複雜項目仍是推薦 Vuex。
  • 不只能夠在父子組件通訊,兄弟組件也能夠實現通訊。
// Father組件
    <template>
      <div class="father"> <h3>父組件Father</h3> 父組件監聽子組件Child的傳值:{{value}} <br> <Child/> <br> <child-b/> </div> </template> <script> import Child from './Child.vue' import ChildB from './ChildB.vue' import eventBus from './bus.js' export default { name: 'father', data () { return { value: '' } }, created() { // 事件監聽 eventBus.$on('change', (value) => { this.value = value }) }, components: { Child, ChildB } } </script> 複製代碼
// Child組件
    <template>
      <div class="child"> <h3>子組件Child</h3> 子組件監聽兄弟組件ChildB的傳值:{{value}} <br> 子組件Child的輸入框:<input type="text" @change="_change"> </div> </template> <script> import eventBus from './bus.js' export default { name: 'Child', data () { return { value: '' } }, created () { // 事件監聽 eventBus.$on('changeB', (value) => { this.value = value }) }, methods: { _change (e) { eventBus.$emit('change', e.target.value) } } } </script> 複製代碼
// ChildB組件
    <template>
      <div class="childB">
        <h3>子組件ChildB</h3>子組件ChildB的輸入框:
        <input type="text" @change="_change">
      </div>
    </template>
    
    <script>
    import eventBus from './bus.js'
    
    export default {
      name: 'ChildB',
      methods: {
        _change (e) {
          eventBus.$emit('changeB', e.target.value)
        }
      }
    }
    </script>
複製代碼

9、$root

當前組件樹的根 Vue 實例。若是當前實例沒有父實例,此實例將會是其本身。iview

  • 經過訪問根組件也能進行數據之間的交互,但極小狀況下會直接修改父組件中的數據。

10、Vuex

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

  • 推薦使用在全局狀態管理多的狀況使用(中大型項目)。
  • 例如路由訪問控制、用戶信息存儲、權限樹、檢測登陸狀態等場景使用Vuex更好。

11、broadcast和dispatch

vue1.0中提供了這種方式,但vue2.0中沒有,但不少開源軟件都本身封裝了這種方式,好比min-ui、element-ui和iview等。

  • broadcast是尋找指定子輩組件,而後觸發事件,可理解爲廣播。
  • dispatch是尋找指定的祖輩組件,而後觸發事件,可理解爲調度。
  • 通常都做爲一個mixins去使用, 本質上這種方式仍是on和emit的封裝,但在一些基礎組件中卻很實用。
function broadcast(componentName, eventName, params) {
      this.$children.forEach(child => {
        // 遍歷子組件
        var name = child.$options.name // 獲取子組件的組件名
    
        if (name === componentName) {
          // 判斷是否是要派發的子組件
          child.$emit.apply(child, [eventName].concat(params)) // 調用子組件的派發方法
        } else {
          broadcast.apply(child, [componentName, eventName].concat([params])) // 不然this交給子組件,尋找孫子組件中是否存在
        }
      })
    }
    export default {
      methods: {
        // 調度
        dispatch(componentName, eventName, params) {
          var parent = this.$parent || this.$root // 獲取父組件
          var name = parent.$options.name /// 獲取父組件的組件名
    
          while (parent && (!name || name !== componentName)) {
            // 判斷父組件是否存在 && (父組件名是否爲空 || 父組件名不等於要派發的組件名)
            parent = parent.$parent // 獲取父組件的父組件
    
            if (parent) {
              // 若是父組件的父組件存在
              name = parent.$options.name // 獲取父組件的父組件的組件名
            }
          }
          // 結束循環
          if (parent) {
            // 判斷有沒有找到要派發的父級組件
            parent.$emit.apply(parent, [eventName].concat(params)) // 調用父級的派發方法
          }
        },
        // 廣播
        broadcast(componentName, eventName, params) {
          broadcast.call(this, componentName, eventName, params) // this指向當前調用該方法的父組件
        }
      }
    }


複製代碼

更多詳細內容,請前往GitHub查看

相關文章
相關標籤/搜索