11月份的面試愈來愈以爲本身學的不夠踏實和深刻。如今實習了有空總結下html
京東前端實習一道面試題:vue中組件通訊接口有哪些,除了props和$emit?前端
注意是接口,我說了vuex和localStorage,立馬就被否決了vue
props最多見的父子通訊接口,可是props是單向數據流的形式:父級 prop 的更新會向下流動到子組件中,可是反過來則不行面試
此時須要藉助vue提供的事件監聽機制來完成子組件向父組件數據流動更新的功能。 在子組件使用$emit定義監聽事件名稱,在父組件使用v-on監聽該事件,在事件中改變父組件的狀態。vuex
// father.vue
<template>
<div> <Children :name="name" @close="closeChildren"><Children /> </div> </template>
<script>
import Children from './children.vue'
export default {
data(){
return {
name: 'vue組件通訊總結'
}
},
components: {
Children
},
methods: {
closeChildren() {
// todo
}
}
};
</script>
複製代碼
// children.vue
<template>
<div> <h3>{{name}}</h3> <el-button v-if="closeBtn" circle size="mini" class="close_btn" icon="el-icon-close" @click="close" ></el-button> </div> </template>
<script>
export default {
props: ["name"],
methods: {
close() {
this.$emit("close");
}
}
};
</script>
複製代碼
$parent用來訪問父組件實例,一般父組件都是惟一肯定的編程
// children.vue
<script>
export default {
created(){
console.log(this.$parent.name) // ==> vue組件通訊總結
this.$parent.name='mmdjj'
console.log(this.$parent.name) // ==> mmdjj
},
};
</script>
複製代碼
$children用來訪問子組件實例,要知道一個組件的子組件多是不惟一的,因此它的返回值是數組.api
// father.vue
<script>
export default {
created(){
console.log(this.$children) // ==> [ VueComponent ]
this.$children.forEach((VueComponent)=>{
if(VueComponent.name == "你知道的組件名稱"){
// todo
}
})
},
};
</script>
複製代碼
refs。數組
refs.refName訪問。瀏覽器
// father.vue
<template>
<div> <Children ref="child1"><Children /> </div> </template>
<script>
import Children from './children.vue'
export default {
data(){
return {
name: '我是父組件name'
}
},
mounted(){
console.log(this.$refs.child1.name) // ==>我是父組件name123
}
};
</script>
複製代碼
// children.vue
<script>
export default {
data(){
return {
name: '我是子組件name'
}
},
mounted(){
this.name = this.$parent.name + '123';
}
};
</script>
複製代碼
注意:ref屬性做爲組件屬性時,訪問的是根組件的實例;做爲dom屬性時,訪問的是dom信息框架
// father.vue
<template>
<div> <Children ref="child1"><Children /> </div> </template>
<script>
import Children from './children.vue'
export default {
data(){
return {
name: 'mmdjj',
age: 18,
sex: 'man'
}
}
};
</script>
複製代碼
這是@2.4新增的屬性和接口。inheritAttrs屬性控制子組件html屬性上是否顯示父組件的提供的屬性
// father.vue
<template>
<div id="father" > <Children :name="name" :age="age" :sex="sex" ><Children /> </div> </template>
<script>
import Children from './children.vue'
export default {
data(){
return {
name: 'mmdjj',
age: 18,
sex: 'man'
}
}
};
</script>
複製代碼
瀏覽器渲染的時候,默認會把父組件寫在子組件的屬性一塊兒渲染出來,它是這樣的
<div id="father" name="mmdjj" age="18" sex="man" >
...
</div>
複製代碼
從@2.4開始,在子組件默認添加inheritAttrs選項,而且默認爲false,來隱藏這些屬性,若是你指望這些屬性是顯示在根html元素上,你只須要將inheritAttrs的值指定爲true
// children.vue
<script>
export default {
inheritAttrs: true
};
</script>
複製代碼
此時渲染以後式這樣的
<div id="father">
...
</div>
複製代碼
$attrs包含全部未在props中聲明的父組件傳遞的屬性
attrs就能夠達到事半功倍的效果,看下面的例子
// father.vue
<template>
<div id="father" > <Children :name="name" :age="age" :sex="sex" ><Children /> </div> </template>
複製代碼
// children.vue
<script>
export default {
props: ["name"]
mounted(){
// 由於在props中聲明瞭name,因此打印結果中沒有name這個屬性
console.log(this.$attrs) // => { "age": 18, "sex": "man" }
}
};
</script>
複製代碼
$attrs還有個妙用就是將父組件全部未在props聲明的屬性經過v-bind傳給本身的內部子組件(將父親的屬性經過本身傳給本身的兒子),也就是說它能夠做爲隔代組件通訊的橋樑,例子以下
// father.vue
<template>
<div> <Children :name="name" :age="age" :sex="sex" ><Children /> </div> </template>
複製代碼
// children.vue
<template>
<div> <Child v-bind="$attrs" ><Child /> </div> </template>
複製代碼
// child.vue
<script>
export default {
props: ["name"]
mounted(){
console.log(this.$attrs) // => { "age": 18, "sex": "man" }
}
};
</script>
複製代碼
下面的全部方法的分類不是惟一的,只是我比較推薦的分類方式,好比$root和依賴注入他們既適合兄弟組件,也適合隔代組件
$root用來方位根實例屬性
我的認爲 root的適用性是最好的,好比前面父子組件通訊你使用了parent或者children,可是因爲需求的改變等等不得已緣由,它們的關係已經不是父子組件了,此時,通訊機制就不能不從新創建了。可是若是你一開始就使用了$root做爲通訊機制,那麼就不存在這樣的麻煩了。
// children1.vue
<script>
export default {
data(){
return {
msg: "hello"
}
},
mounted(){
this.$root.msg = this.msg
}
};
</script>
複製代碼
// children2.vue
<script>
export default {
mounted(){
console.log(this.$root.msg) // => 'hello'
}
};
</script>
複製代碼
確切的說$root方法使用於任何狀況的組件通訊,包括父子組件、兄弟組件、隔代組件通訊,能夠形象的把它理解成爲它們共同的祖先
這時候聰明的你確定聯想到了根組件,那個被叫作App.vue的傢伙。好奇它和$root到底是啥關係,看下面的代碼
// App.vue
<script>
export default {
mounted(){
console.log(this.$root == this) // => ?
}
};
</script>
複製代碼
實際上這個打印的結果是false,也就說$root也是App.vue的祖先
$root也有它的缺點,官網中也提到了,它只適合通用化(就是不用動態更新的意思)的場景,若是想創建隨着改變更態更新的數據,建議使用vuex
// father.vue
<script>
import Child1 from "@/components/children";
export default {
data() {
return {
name: "123",
age: 23,
say: "hello"
};
},
beforeMount() {
this.$root.testMeg = 'mmdjj';
},
components: {
Child1
}
};
</script>
複製代碼
// Child1.vue
<template>
<div>
{{$root.testMsg.name}} // 一直顯示mmdjj
</div>
</template>
<script>
export default {
mounted() {
setTimeout(() => {
this.$root.testMeg = "welcome";
console.log(this.$root.testMeg); // welcome
}, 3000);
}
};
</script>
複製代碼
不過你嘗試着給$root傳遞一個響應式的對象,當對象中的數據改變時,其他使用這個屬性的地方也會跟着改變,也就是說它就是響應式的了
// father.vue
<script>
import Child1 from "@/components/children";
export default {
data() {
return {
name: "123",
age: 23,
say: "hello",
testMeg: {
name: "mmdjj"
}
};
},
beforeMount() {
this.$root.testMeg = this.testMeg;
},
components: {
Child1
}
};
</script>
複製代碼
// Child1.vue
<template>
<div>
{{$root.testMsg.name}} // 剛開始顯示mmdjj,三秒以後顯示爲welcome
</div>
</template>
<script>
export default {
mounted() {
setTimeout(() => {
this.$root.testMeg.name = "welcome";
console.log(this.$root.testMeg.name); // welcome
}, 3000);
}
};
</script>
複製代碼
eventBus並非vue官方的名稱,它是使用vue實例的$emit接口創建全局的事件監聽機制,不少人巧妙的使用它來組件通訊,這種思想來源於Android事件發佈/訂閱輕量級框架eventBus。可是這並非vue最優的通訊機制。
本質就是實例化一個空vue實例
// src/eventBus.js
import Vue from "vue"
const eventBus = new Vue()
export default eventBus
複製代碼
或者直接掛載到全局,連引入均可以省略
// main.js
import Vue from "vue"
Vue.prototype.$eventBus = new Vue()
複製代碼
通常這種方式每一個都會經歷三個階段,發起事件——>監聽事件——>銷燬事件
發起
// children1.vue
// 這是掛載到全局的版本
<script>\
export default {
mounted(){
// 這是單獨文件的版本(注意:引入省略了)
eventBus.$emit("update", this.msg)
// 這是掛載到全局的版本
this.$eventBus.$emit("update", this.msg)
}
};
</script>
複製代碼
監聽
// children2.vue
<script>
export default {
mounted(){
// 這是單獨文件的版本(注意:引入省略了)
eventBus.$on("update", this.updateMsg(msg))
// 這是掛載到全局的版本
this.$eventBus.$on("update", this.updateMsg(msg))
},
methods: {
updateMsg(msg){
console.log(msg)
// todo
}
}
};
</script>
複製代碼
銷燬
// children2.vue
<script>
export default {
beforeDestroy(){
// 這是單獨文件的版本(注意:引入省略了)
eventBus.$off("update")
// 這是掛載到全局的版本
this.$eventBus.$off("update")
}
};
</script>
複製代碼
看了前面的例子,你會發現,通訊最關鍵的地方其實就在上面的updateMsg函數裏,而這以外的全部的東西,都只是一種繁瑣的鋪墊,因此缺點也是顯而易見的。另外對於多個監聽的地方,你還須要手動關閉,還有一個被你們詬病的就是每一個事件都必需起一個獨一無二的名字,這對起名廢的同窗來講是災難啊
有人已經提供了一個方案使用eventBus替代vuex,這個方案爲咱們解決了前面提到的缺點,具體能夠看這裏
依賴注入是在provide選項中提供要共享的數據,在inject選項中使用共享的數據。它也是官方首推在不使用vuex時隔代組件通訊方式
// 父級組件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 任何後代組件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
複製代碼
須要知道的是,provide也能夠是個函數,返回一個對象,更多細節能夠參考詳細的api文檔 此外,它也和props同樣能夠設置默認值
const Child = {
inject: {
foo: { default: 'foo' }
}
}
// 或者
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
複製代碼
出於設計的考慮,依賴注入和$root同樣,都是非響應式的數據模式
可是官方又說了,能夠經過提供一個響應式的對象,來使注入的數據是響應式的。
// 父級組件提供 'foo'
var Provider = {
data(){
return {
obj: {name: 'mmdjj'}
}
},
provide: {
foo: this.obj
},
}
// 任何後代組件注入 'foo'
var Inject = {
template: "<div>{{foo.name}}</div>", // => 三秒後由mmdjj變成welcome
inject: ['foo'],
created () {
console.log(this.foo.name) // => "mmdjj"
setTimeout(() => {
this.foo.name = "welcome";
console.log(this.foo.name); // welcome
}, 3000);
}
// ...
}複製代碼