通信方式:
> props(常用)
> props和$emit(常用)
> .sync(語法糖)
> model(單選框和復選框場景可以使用)
> $attr和$listeners(組件封裝用的比較多)
> provide和inject(高階組件/組件庫使用比較多)
> eventBus(小項目中使用就好)
> Vuex(中大型項目推薦使用)
> $parent和$children(推薦少用)
> $root(組件樹的根,用的少)
> 其他通信方式
一、props
當前組件接收到的 props 對象。Vue 實例代理了對其 props 對象屬性的訪問。
在使用prop傳參時需要注意:
- 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>
二、props和$emit
觸發當前實例上的事件。附加參數都會傳給監聽器回調。
emit的使用場景主要是在子組件要傳參數給父組件,通過$emit來觸發父組件給的監聽器。
// 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>
三、.sync語法糖(2.3.0+ 新增)
在有些情況下,我們可能需要對一個 prop 進行“雙向綁定”。不幸的是,真正的雙向綁定會帶來維護上的問題,因為子組件可以修改父組件,且在父組件和子組件都沒有明顯的改動來源。因此以 update:myPropName 的模式觸發事件取而代之。
- 注意帶有 .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>
四、model(2.2.0 新增)
允許一個自定義組件在使用 v-model 時定制 prop 和 event。默認情況下,一個組件上的 v-model 會把 value 用作 prop 且把 input 用作 event,但是一些輸入類型比如單選框和復選框按鈕可能想使用 value prop 來達到不同的目的。使用 model 選項可以回避這些情況產生的沖突。
// 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>
五、$attrs和$listeners (2.4.0 新增)
- $attrs包含了父作用域中不作為 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部組件——在創建高級別的組件時非常有用。
- $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>
六、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