vue组件通讯总结

11月份的面试愈来愈以为本身学的不够踏实和深刻。如今实习了有空总结下html

京东前端实习一道面试题:vue中组件通讯接口有哪些,除了props和$emit?前端

注意是接口,我说了vuex和localStorage,立马就被否决了vue

1、父子组件通讯

1.props和$emit

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>
复制代码

2.parent和children或者$refs

1) $parent

$parent用来访问父组件实例,一般父组件都是惟一肯定的编程

// children.vue
<script>
export default {
    created(){
        console.log(this.$parent.name) // ==> vue组件通讯总结
        this.$parent.name='mmdjj'
        console.log(this.$parent.name) // ==> mmdjj
    },
};
</script>
复制代码
2) $children

$children用来访问子组件实例,要知道一个组件的子组件多是不惟一的,因此它的返回值是数组.api

// father.vue
<script>
export default {
    created(){
        console.log(this.$children) // ==> [ VueComponent ]
        this.$children.forEach((VueComponent)=>{
            if(VueComponent.name == "你知道的组件名称"){
                // todo 
            }
        })
    },
};
</script>
复制代码
3) $refs

children的缺点是没法肯定子组件的顺序,也不是响应式的。因此若是你确切的知道要访问的子组件建议使用refs。数组

refs须要使用ref属性在子组件上设置标识,而后经过this.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>
复制代码

3.inheritAttrs和$attrs

1)inheritAttrs

这是@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>
复制代码
2)$attrs

$attrs包含全部未在props中声明的父组件传递的属性

attrs简单的说就是props的增强版,由于当父组件提供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和依赖注入他们既适合兄弟组件,也适合隔代组件

2、兄弟组件通讯

1.$root

$root用来方位根实例属性

1)基本用法

我的认为 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的祖先

2)一个缺点

$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>
复制代码
3)让它成为响应式

不过你尝试着给$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>
复制代码

2.eventBus

eventBus并非vue官方的名称,它是使用vue实例的$emit接口创建全局的事件监听机制,不少人巧妙的使用它来组件通讯,这种思想来源于Android事件发布/订阅轻量级框架eventBus。可是这并非vue最优的通讯机制。

1建立

本质就是实例化一个空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()
复制代码
2.使用

通常这种方式每一个都会经历三个阶段,发起事件——>监听事件——>销毁事件

发起

// 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>
复制代码
这里须要特别强调的一点是当一个事件在多个组件里监听时,每一个组件在销毁时连同事件也要销毁,否则它会在你看不到的地方继续执行而难以被发现,固然不论是不是在多个组件监听,在组件销毁时一块儿销毁监听事件不失为一种良好的编程习惯
3.缺点

看了前面的例子,你会发现,通讯最关键的地方其实就在上面的updateMsg函数里,而这以外的全部的东西,都只是一种繁琐的铺垫,因此缺点也是显而易见的。另外对于多个监听的地方,你还须要手动关闭,还有一个被你们诟病的就是每一个事件都必需起一个独一无二的名字,这对起名废的同窗来讲是灾难啊

4.替代vuex

有人已经提供了一个方案使用eventBus替代vuex,这个方案为咱们解决了前面提到的缺点,具体能够看这里

3、隔代组件通讯

1.provide和inject(依赖注入)

依赖注入是在provide选项中提供要共享的数据,在inject选项中使用共享的数据。它也是官方首推在不使用vuex时隔代组件通讯方式

1)使用
// 父级组件提供 '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]
    }
  }
}
复制代码
2)不明显的缺点

出于设计的考虑,依赖注入和$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);
  }
  // ...
}复制代码
相关文章
相关标签/搜索