Vue組件基礎與通訊

Vue組件基礎與通訊

1、vue cli腳手架

① vue cli 簡介與安裝html

vue cli 3.0以前安裝的是vue-cli模塊,vue cli 3.0以後安裝的是 @vue/cli模塊。
若是已經全局安裝了舊版本的 vue-cli , 那麼須要 先卸載vue-cli,再 全局安裝 @vue/cli
雖然安裝的是vue cli,可是 執行的命令仍然是vue
npm uninstall vue-cli -g // 卸載舊版本腳手架

npm install -g @vue/cli // 安裝新版本腳手架

vue --version // 檢測是否安裝成功

② vue cli 的簡單使用vue

  • 經過create命令建立一個由 vue-cli-service 提供支持的新項目。即會經過vue-cli-service去啓動vue項目,從package.json文件中能夠看到npm run serve實際執行的是vue-cli-service serve
vue create vue-test // 在當前目錄下建立一個vue-test項目,經過問卷的方式選擇好須要安裝的模塊後會自動安裝並初始化vue項目

cd vue-test // 進入項目根目錄下

npm run serve // 啓動vue項目
  • 經過vue serve或者vue build直接啓動vue項目,執行vue serve命令的時候能夠指定一個.vue文件或者.js文件做爲啓動入口,若是沒有指定啓動入口文件,那麼就會自動在當前目錄下自動尋找main.js、index.js、App.vue 或 app.vue入口文件。
若是入口文件是 .js文件,那麼其中 必須建立一個Vue實例,而且 必須添加el屬性,且其屬性值 必須爲"#app",而且 添加render屬性渲染一個Vue組件以便可以顯示組件內容。
若是入口文件是 .vue文件,那麼能夠直接渲染,.vue文件中的template裏面 必需要有一個根元素標籤,不限於<div>,可是不須要添加id爲app的根元素,即 根元素內容能夠任意書寫

// main.jsvue-cli

import Vue from "vue";
import App from "./App";

const vm = new Vue({
    el: "#app", // 固定爲#app,不然沒法顯示App組件內容
    render: h => h(App)
});
export default vm; // 能夠不對外暴露

2、父子組件通訊

若是父組件要向子組件傳遞數據,那麼只須要在子組件中添加一個props屬性,其屬性值能夠是一個數組,數組元素爲一個字符串,即父組件給子組件傳遞數據時所使用的名稱,直接在子組件標籤中看成元素的一個屬性名使用;props屬性值也能夠是一個對象,對象的屬性名爲父組件給子組件傳遞數據時所使用的名稱,屬性值爲一個對象,用於控制父組件傳遞數據的類型、默認值
// 父組件 Parent.vuenpm

<template>
    <div>
        Parent父組件: {{firstName}}
        <!--若是子組件的屬性名前不帶冒號,則傳遞是原字符串;若是帶冒號則傳遞的是當前組件中該屬性名對應的值,即this.firstName的值;-->
        <Son :value="firstName"></Son> 
        <!-- 若是該屬性名在該組件中不存在對應的值,那麼雖然不會報錯,可是子組件獲取的值將爲null,即獲取不到值 -->
        <Son :value="firstName1"></Son> <!--父組件上不存在firstName1屬性,子組件沒法獲取到value對應的值 -->
    </div>
</template>

// 子組件 Son.vuejson

<template>
    <div>
        Son組件: {{value}}
        <button @click="change">修改子組件數據</button>
    </div>
</template>
<script>
export default {
    mounted () {
        console.log(this.value); // props中定義的屬性名也會添加到vue實例上,能夠直接經過vue組件實例獲取到 
    },
    props: ["value"], // Son組件上定義了一個value屬性,用於接收來自父組件傳遞過來的數據,能夠子組件中能夠直接使用
    // props: { // 對象的形式
    //     value: {
    //         type: String // 只能傳遞字符串類型,String是大寫
    //     }
    // }
    methods: {
        change() {
            this.value = "zhang"; // 直接修改父組件的值是不容許的
        }
    }
}
</script>
注意,上面子組件中添加了一個按鈕用於修改value的屬性值,因爲Vue規定 子組件不能直接去修改父組件的值,因此會報錯。即 父子組件中的數據是單向的。子組件中若是確實想要修改父組件傳遞過來的數據,那麼能夠在子組件中定義一個新的變量,將父組件傳遞過來的數據保存起來,這個時候子組件去修改這個新的變量對應的值就能夠了,可是這樣 子組件修改的值不會同步到父組件中,由於 子組件修改的是本身的數據了

3、父子組件數據同步

① 能夠在父組件中使用子組件的時候,在子組件標籤上監聽一個事件,這個事件名能夠任意,好比input事件,這樣當子組件內部發射該事件後,子組件就能監聽到該事件就能夠直接調用父組件中的方法進行處理,如:
//父組件 Parent.vue數組

<!-- 在子組件上監聽一個input事件,可是其事件處理函數是父組件中的函數 -->
<Son :value="firstName" @input="change"></Son>

// 子組件,Son.vueapp

<button @click="change">修改父組件數據</button>
 methods: {
        change() {
            this.$emit("input", "zhang");// 子組件內部發射一個input事件
        }
 }
在父組件使用子組件的時候,給子組件標籤上添加了@input="change",子組件渲染的時候,就會在子組件上監聽該事件,就至關於 子組件.$on("input", change),即 子組件監聽到input事件後會觸發父組件上change函數的執行,因此該種方式其實就是利用 父子組件單向數據流特性,由 子組件發起事件,經過 修改父組件中的數據來實現父子組件數據的同步, 本質就是修改父組件數據

② 父組件向子組件傳遞數據的時候,使用sync修飾符來修飾子組件上用於接收父組件數據的變量名,同時子組件內部發射一個update:value事件也能夠實現父子組件數據的同步更新,如:
// 父組件 Parent.vueide

<Son :value.sync="firstName"></Son> 
<!-- <Son :value="firstName" @update:value="change"></Son>  --><!--兩者等價-->

// 子組件 Son.vue函數

methods: {
        change() {
            this.$emit("update:value", "zhang"); // 子組件發射update:value事件
        }
}
sync修飾符其實就是 在子組件上綁定值的同時監聽了@update:value事件,是一種語法糖,可是 子組件上必須發射固定名稱的update:value事件纔會起做用
須要注意的是,這裏所謂的固定名稱中 value是不固定的,value只是一個子組件用於接收父組件數據時所定義的變量(屬性),好比子組件上定義的用於接收父組件的變量是 surname,那麼子組件就要發射 "update:surname"事件了,父組件傳遞數據的時候就要使用: surname.sync="firstName"了

③ 若是子組件上定義的用於接收父組件數據的屬性(變量)是value,而且在子組件中監聽的是@input事件,那麼咱們能夠直接簡寫成v-model,由於v-model實際就是綁定value的值而且監聽@input事件,如:
// 父組件 Parent.vueui

<Son v-model="firstName"></Son>

// 子組件 Son.vue

methods: {
        change() {
            this.$emit("input", "zhang");// 必須是發射input事件
        }
    }
sync和v-model都能實現父子組件數據的同步,可是v-model相對比較侷限,屬性名必須是 value,事件名必須是 input,而sync修飾符屬性名能夠任意。

④ 若是是三級組件通訊,該如何處理?好比父組件與孫子組件通訊。
一樣,咱們也能夠利用單向數據流的原理,咱們只要可以改變父組件上的數據,那麼兒子組件和孫子組件上的數據都會進行相應的修改了,而前面父子組件通訊的時候是經過子組件發射一個input事件來調用父組件的方法去改變父組件上的數據的,因爲孫子組件直接發射一個input事件,父組件上是監聽不到,由於父組件上監聽的是子組件內部發射的input事件,可是咱們能夠經過孫子組件的$parent屬性獲取到子組件,而後經過子組件去發射input事件,那麼父組件就能監聽到input事件了,從而改變父組件中的數據,實現三級組件通訊,如:
// 孫子組件 Grandson.vue

<div>
        Grandson組件: {{value}}
        <button @click="changeParent">修改個人父組件數據</button>
 </div>
 methods: {
        changeParent() {
            this.$parent.$emit("input", "Wang");// 經過孫子組件的父組件去發射input事件
        }
 }

⑤ 若是是多級組件通訊呢?我也能夠經過前面三級組件通訊原理,咱們只要遍歷當前組件的全部祖先組件,而後讓全部祖先組件都發射一個input事件,這樣一層一層發射input事件,那麼最終頂層的父組件確定可以收到一個input事件從而改變頂層父組件的數據,實現多級組件之間的通訊。如:
// main.js 在Vue原型對象上添加一個$dispatch方法,方便後輩組件調用

Vue.prototype.$dispatch = function (eventName, data) { // 切勿使用箭頭函數
    let parent = this.$parent; // 獲取調用$dispatch方法的父組件
    while(parent) { 遍歷祖先組件,一層一層發射相應的事件
        parent.$emit(eventName, data);
        parent = parent.$parent;
    }
}

// 後輩組件

methods: {
        changeParent() {
            this.$dispatch("input", "Wang"); // 派發一個input事件,其祖先組件都會發生input事件
        }
    }

⑥ 有後輩組件向祖先組件派發事件,天然有祖先組件向後輩組件廣播事件,所謂廣播就是祖先組件通知監聽了某個事件的組件都執行一下對應的事件函數,如:
// main.js 在Vue原型對象上添加一個$broadcast方法,方便全部組件調用

Vue.prototype.$broadcast = function(eventName, data) {
    const broadcast = (children) => { // 遞歸調用broadcast方法
        children.forEach((child) => { // 遍歷子組件,每一個子組件都發射一個指定的事件
            child.$emit(eventName, data);
            if (child.$children) { // 若是子組件上還有子組件
                broadcast(child.$children) // 遞歸調用
            }
        });
    }
    broadcast(this.$children);
}

v-bind和$attrs、v-on和$listenners的用法
當咱們在組件內部並無定義props屬性來接收父組件傳遞過來的數據時,這些非prop聲明的屬性將會原封不動的添加到組件渲染後的html標籤上,如:
// Parent.vue父組件

<!--父組件傳遞了 value、name、age三個屬性給Son組件-->
<Son :value="firstName" @input="change" :name="name" :age="age"></Son>

Son.vue 子組件

export default {
    props: ["value"] // 子組件上只定義了一個value屬性用於接收父組件上的數據
}

子組件渲染完成後,除value屬性外,name和age屬性都原封不動添加到了html標籤上,如:

<div name="Si" age="18">
    Son組件: Li
</div>
若是不想這些父組件傳遞過來的非prop屬性出如今html標籤上,那麼能夠 在子組件上添加一個 inheritAttrs屬性,而且屬性值設置爲 false
export default {
    inheritAttrs: false, // 不在html標籤上繼承非prop屬性
    props: ["value"] // 子組件上只定義了一個value屬性用於接收父組件上的數據
}
須要注意的是雖然設置了inheritAttrs爲false,可是子組件上仍是具備$attrs屬性的,經過$attrs屬性仍是能夠獲取到那些來自父組件傳遞過來的非prop屬性的, $attrs爲一個對象,對象屬性名爲非prop屬性名,如上子組件上的$attrs值爲{name: "Si", age: 18}

若是v-bind不綁定屬性,直接賦值一個對象的時候,那麼其會將對象的屬性名看成組件的屬性名,將對象的屬性值傳遞給組件,如:
// Son.vue

<Grandson :value="value" v-bind="{name: 'Si', age: 18 }"></Grandson>
<!--其等價於-->
<Grandson :value="value" name='Si' age=18></Grandson>
因此若是父組件傳遞給子組件的數據,子組件不想用( 子組件並未定義相應的props屬性進行接收),可是孫子組件想用,那麼能夠 經過v-bind直接傳遞給孫子組件,如:
// Son.vue
<Grandson :value="value" v-bind="$attrs"></Grandson>
一樣的,還有$listeners,其包含的是
父做用域中不含.native修飾的事件監聽器,如上面例子,在父做用域中的Son組件上監聽了input事件,那麼Son組件內就能夠經過$listeners.input()執行input的事件函數。
固然也能夠經過 v-on="$listeners" 傳遞給孫子組件使用
其主要區別就是: $attrs是組件上屬性的集合,$listeners是組件上方法(事件)的集合

⑧ provide和inject,提供和注入實現祖先組件和後代組件之間通訊。

能夠經過 provide()提供一個對象數據到父組件上,而後其後代組件就能夠經過 inject將祖先組件上的數據注入到後代組件中

// Parent.vue

export default {
    provide() { // 提供一個數據到祖先組件上
        return {
            money: 1000000
        }
    }
}

// Grandson.vue

export default {
    inject: ["money"] // 在後代組件中注入提供到祖先組件上的數據
}
// Grandson組件就能夠直接經過this.money獲取到數據了

⑨ 獲取子組件或者子標籤的引用,父組件能夠經過$refs屬性獲取到添加了ref屬性的子組件或者子標籤對象,而後進行相應的操做,如:

<Son :value="firstName" @input="change" ref="son"></Son>
export default {
    mounted() {
        this.$refs.son.say(); // 經過$refs獲取到<Son>組件,而後調用其say方法
    }
}

⑩ 經過eventBus進行通訊,所謂eventBus就是一個公共的Vue實例,全部組件都經過這個公共的Vue實例進行發射和監聽事件,如:

Vue.prototype.$bus = new Vue(); // 將這個eventBus對象暴露到原型上方便調用
全部組件均可以獲取到這個$bus對象並進行收發數據通訊了。
相關文章
相關標籤/搜索