1. 程式人生 > >vue2.X基礎知識四之元件及父子元件通訊

vue2.X基礎知識四之元件及父子元件通訊

一、元件註冊

1、在Vue中全域性註冊

//my-component就是註冊的元件自定義標籤名,推薦使用小寫加減號分割的形式命名
//template的DOM結構必須被一個元素包含(這裡是div),否則無法渲染
Vue.component('my-component', {template: '<div>我是元件</div>'}); 
   var app = new Vue({
            //選擇元素
            el: "#app"
        });

在html中使用元件:

<div id="app">
    <my-component></my-component>
</div>

使用全域性註冊的時候,元件必須先註冊,然後再例項化Vue;全域性註冊的元件,任何Vue例項都可以使用。

2、在Vue例項中,註冊區域性元件

註冊的區域性元件只有在該例項作用域下有效。元件也可以使用components選項來註冊元件,使元件可以巢狀。

<div id="app">
    <my-component></my-component>
</div>
var child = {
    template: '<div>區域性註冊元件的內容</div>'
}

var app = new Vue({
    el: '#app',
    components: {
        'my-component': child
    }
});

3、元件中的data

元件中的配置選項與Vue例項很類似,在元件中使用data時,data必須是函式,然後將資料return出去,例如:

<div id="app">
    <my-component></my-component>
</div>
Vue.component('my-component', {
    		template: '<div @click="handleClick($event)">{{ name }}</div>',
    		
    		/* 子元件的data必須是一個函式  */
    		data: function() {
    			return {
    				name: '子元件的資料'
    			}
    		},
    		methods: {
    			handleClick($event){
    				$event.target.style.color = 'red';
    				alert('子元件的click事件');
    			}
    		}
    	});
    	
        var app = new Vue({
            el: "#app"     
        });
二、元件通訊

    元件不僅僅是要把模板的內容進行復用,更重要的是元件間要進行通訊。通常父元件的模板中包含子元件,父元件要正向地向子元件傳遞資料或引數,子元件接收到後根據引數的不同來渲染不同的內容或執行操作。props的值可以是兩種,一種是字串陣列,一種是物件。

1、父元件向子元件傳遞資料,使用props來實現

    在元件中,使用選項props來宣告需要從父級接收的資料。我們來看一個最簡單的例子,直接給子元件傳遞一個字串:

<div id="app">
    <my-component message="來自父元件的資料"></my-component>		
</div>
Vue.component('my-component', {
    props: ['message'],
    template: '<div>{{ message }}</div>'
});
    	        
var app = new Vue({
    el: "#app"
});

上面程式碼中,我們給子元件設定了一個自定義的屬性message,它的值是父元件想要傳遞給子元件my-component的值,這裡是固定的一個字串。執行結果如下:

說明:由於html特性不區分大小寫,當使用DOM模板時,駝峰命名的props名稱要轉為短橫分隔命名,比如props:['warningText'],在元件定義這個屬性時候應該這樣寫:warning-text="來自父元件的資訊" (如果使用的是字串模板(單個vue檔案),可忽略這些限制)

  props中宣告的資料與元件data函式return的資料主要區別就是props的來自父級,而data中的是元件自己的資料,作用域是元件本身,這兩種資料都可以在模板template及計算屬性computed和方法methods中使用

  有時候,傳遞的資料並不是直接寫死的,而是來自父級的動態資料,這時可以使用指令v-bind來動態繫結props的值,當父元件的資料變化時,也會傳遞給子元件。

<!--這裡用v-model綁定了父級的資料parentMessage,當通過輸入框任意輸入時,
子元件接收到的props "message"也會實時響應,並更新元件模板。 -->
<div id="app">
    <input type="text" v-model="parentMessage" />
    <my-component :message="parentMessage"></my-component>
</div>
Vue.component('my-component', {
    		props: ['message'],
    		template: `<div>
    			      <p>子元件自身的資料:{{ name }},</p>
    			      <p>來自父元件的資料:<b style="color: red;">{{ message }}</b></p>
    		          </div>`,
    		data: function() {
    			return {
    			    name: '我是子元件自帶的資料'
    			}
    		}
    	});
    	
        var app = new Vue({
            el: "#app",
            data: {
            	parentMessage: '我是父元件的message',
            }
});

可以看到,這個例子與上一個例子有個很明顯的區別,這裡使用v-bind綁定了自定義的屬性message的值,message儲存了父元件parentMessage的值;子元件通過props接收message儲存的值,同時template可以像訪問data一樣,訪問props裡面的message;輸出效果如下:

注意、如果直接傳遞數字,布林值、陣列、物件,而且不使用v-bind,傳遞的僅僅是字串;例如:

    <div id="app">
        <!-- 第一個元件沒有使用v-bind,所以傳遞給子元件的是字串"[1,2,3]",所以,輸出的length是7 -->
        <my-component message="[1,2,3]"></my-component>
        <!-- 這裡是正常輸出,length為3 -->
        <my-component :message="[1,2,3]"></my-component>
    </div>
    <script>
    	Vue.component('my-component', {
    		template: `<div>
    			      <p>來自父元件的資料:{{ message.length }}</p>
    		          </div>`,
    		props: ['message'],
    		/* 子元件的data必須是一個函式  */
    		data: function() {
    			return {
    			    name: '我是子元件自帶的資料'
    			}
    		}
    	});
        var app = new Vue({
            //選擇元素
            el: "#app",
            data: {
            	parentMessage: '我是父元件的message',
            }
        });
    </script>

props單向資料流

Vue2.X通過props傳遞資料是單向的,也就是父元件資料變化時會傳遞給子元件,但是反過來不行。通常在業務中經常會遇到兩種需要改變prop的情況,程式碼如下:

<div id="app">
    <my-component :init-count="parentData"></my-component>
		
    <!-- 在子元件中自定義屬性最好不要與html元素已有的屬性名一樣 -->
    <my-component-2 :styles="style"></my-component-2>
</div>
<script>
    	/*
    	 需要改變prop的情況一:
    	    父元件傳遞初始值過來,子元件將它作為初始值儲存起來,在自己的作用域下
    	 可以隨意使用和修改。這種情況可以在元件data內再宣告一個數據,引用父元件的prop;   
    	 * 
    	 * */
    	Vue.component('my-component', {
    		props: ['initCount'],
    		template: '<div>{{ count }} <input type="text" v-model="count" /></div>',
    		data: function () {
    			return {
    				count: this.initCount
    			}
    		}
    	});
    	
    	/*
    	 另一種情況就是prop作為需要被轉變的原始值傳入。這種情況用計算屬性就可以了;
    	 用css傳遞寬度要帶單位(px),但是每次都寫太麻煩,而且數值計算一般是不帶單位的,
    	 所以統一在元件內使用計算屬性就可以了。
    	 * */
    	Vue.component('my-component-2', {
    		props: ['styles'],
    		template: '<div :style="style">元件內容</div>',
    		computed: {
    			style: function () {
    				return {
    					width: this.styles.width + 'px',
    					color: this.styles.color,
    					border: this.styles.border
    				}
    			}
    		}
    	});
    	
    	
    	var app = new Vue({
    		el: '#app',
    		data: {
    			parentData: 1,
    			style: {
    				color: '#e6b5a7',
    				width: 600,
    				border: '1px solid #e7e8e9'
    			}
    		}
    	}); 	
</script>

以上兩種情況,子元件獲取父元件的資料之後隨意修改,都不會再影響父元件的資料。
props資料驗證

    一般當你的元件需要提供給別人使用時,推薦都進行資料驗證;如果傳入的值型別不對,就會在控制檯彈出警告。例如:

<div id="app">
    <my-component :prop-a="parentData" :prop-d="count"></my-component>
</div>
Vue.component('my-component', {
	                    template: '<div>props的值為陣列時,接收父元件資料:{{ propA }},接收數值型別:{{ propD }}</div>',
			    props: {
				// 必須是數字型別
				propA: Number,
				// 必須是字串或數字型別
				propB: [String, Number],
				// 布林值,如果沒有定義,預設值就是true
				propC: {
					type: Boolean,
					default: true
				},
				//數字,而且是必傳
				propD: {
					type: Number,
					required: true
				},
				//如果是陣列或物件,預設值必須是一個函式來返回
				propE: {
					type: Array,
					default: function () {
						return [];
					}
				},
				//自定義一個驗證函式
				propF: {
					validator: function (value) {
						return value > 10;
					}
				}
			}
		});
    	var app = new Vue({
    		el: '#app',
    		data: {
    			parentData: 123,
    			count: 456
    		}
    	});

以上程式碼如果我們在子元件裡不傳入prop-d,就會在控制檯報錯,因為propD是必傳項;驗證的type型別可以是:String,Number,Boolean,Object,Array,Function;type也可以是一個自定義構造器,使用instanceof檢測。

2、子元件向父元件傳遞資料,使用自定義事件

 子元件需要向父元件傳遞資料時,就要用到自定義事件;v-on除了監聽DOM事件外, 還可以用於元件之間的自定義事件。

 子元件用$emit來觸發事件,父元件用$on()來監聽子元件的事件。 父元件可以直接在子元件的自定義標籤上使用v-on(語法糖@)來監聽子元件觸發的自定義事件

<div id="app">
    <p>總數:{{ total }}</p>
    <my-component 
		@increase="handleGetTotal"
		@reduce="handleGetTotal"></my-component>
</div>

以上程式碼使用@(v-on)監聽子元件的increase和reduce自定義事件,一旦子元件觸發了這兩個事件,父元件就會立即執行handleGetTotal方法,同時接收子元件傳遞過來的引數(資料);

		Vue.component('my-component', {
			template: `
				<div>
					<button @click="handleIncrease">+1</button>
					<button @click="handleReduce">-1</button>
				</div>
			`,
			data: function () {
				return {
					counter: 0
				};
			},
			methods: {
				handleIncrease: function () {
					this.counter++;
					// 觸發自定義的increase事件,同時將自身的counter資料傳遞過去
					this.$emit('increase', this.counter);
				},
				handleReduce: function () {
					this.counter--;					
					// 觸發自定義的reduce事件
					this.$emit('reduce', this.counter);
				}
			}
		});
		
		var app = new Vue({
			el: '#app',
			data: {
				total: 0
			},
			methods: {
				handleGetTotal: function (total) {
					this.total = total;
				}
			}
		});
子元件在按鈕上繫結click事件,當點選按鈕時,執行自身methods裡的方法,這兩個方法使用vue例項的$emit來觸發自定義事件increase或reduce;

除了用v-on在元件上監聽自定義事件外,也可以監聽DOM事件,這時可以用.native修飾符表示監聽的是一個原生事件,監聽的是該元件的根元素。例如:

<div id="app">
    <!-- 監聽原生事件 -->
    <my-component v-on:click.native="handleClick"></my-component>
</div>
var app = new Vue({
    el: '#app',
    data: {
	total: 0
    },
    methods: {
		handleGetTotal: function (total) {
		    this.total = total;
		},
		handleClick: function () {
		    alert('監聽子元件的原生click事件的處理函式要寫在父元件上');
		}
	}
});

Vue 2.X可以在自定義元件上使用v-model指令,示例如下:

<div id="app">
		<p>總數:{{ total }}</p>
        <my-component v-model="total"></my-component>
        
        <my-component-2 @input="getParentData"></my-component-2>
    </div>
    <script>
    	Vue.component('my-component', {
    		template: '<button @click="handleClick">+1</button>',
    		data: function () {
    			return {
    				counter: 0
    			}
    		},
    		methods: {
    			handleClick: function () {
    				this.counter++;
    				this.$emit('input', this.counter);
    			}
    		}
    	});
    	
    	Vue.component('my-component-2', {
    		template: '<button @click="handleIncrease">+1</button>',
    		data: function () {
    			return {
    				counter: 0
    			}
    		},
    		methods: {
    			handleIncrease: function () {
    				this.counter++;
    				this.$emit('input', this.counter);
    			}
    		}
    	});
    	
    	//以上兩種寫法,效果是一樣的;只是v-model更簡潔,方便,不需要使用自定義屬性
    	
        var app = new Vue({
            el: "#app",
            data: {
            	total: 0
            },
            methods: {
            	getParentData: function (total) {
            		this.total = total;
            	}
            }
        });
    </script>

使用v-model指令建立自定義的表單輸入元件:

<div id="app">
		<p>總數:{{ total }}</p>
        <my-component v-model="total"></my-component><br/>
        <button @click="handleReduce">-1</button>
</div>
Vue.component('my-component', {
    		props: ['value'],
    		template: '<input :value="value" @input="updateValue" />',
    		methods: {
    			updateValue: function (event) {
    				this.$emit('input', event.target.value);
    			}
    		}
    	});
    	
        var app = new Vue({
            el: "#app",
            data: {
            	total: 0
            },
            methods: {
            	handleReduce: function () {
            		this.total--;
            	}
            }
});

以上程式碼執行過程個人理解:

通過v-model和props將父元件的total值傳遞給了input;

 input使用v-bind綁定了父元件傳遞過來的值;同時綁定了input事件,當輸入新的input值時,通過updateValue處理函式

 執行$emit('input', event.target.value)將輸入的新值傳回給父元件的total;(這裡面發生了父元件向子元件傳值以及子元件給父元件傳值的過程,都得益於v-model)

顯示效果如下:

文章說明:本文大部分內容來自書籍《Vue.js實戰》