vue组件通信进阶

序言:我们都知道vue父子组件通信主要通过props和事件,那还知道其他形式的通讯方式吗?本文将一一为你揭晓。1、sync修饰符+this.$emit('update:属性名', data)1.1、sync修饰符的作用在Vue中,子父组件最常用的通信方式就是通过props进行数据传递,props值只能在父组件中更新并传递给子组件,在子组件内部,是不允许改变传递进来的props值,这样做是为了保证数据单向流通。但有时候,我们会遇到一些场景,需要在子组件内部改变props属性值并更新到父组件中,这时就需要用到.sync修饰符。 如果我们在子组件中直接修改props中的属性值,将会报以下错误: 报错的大概意思就是:不允许直接修改props里面的属性值。 这里可以参考一篇文章:https://blog.csdn.net/XuM2222... 1.2、利用sync修饰符实现双向数据绑定父组件:通过给绑定属性添加sync修饰符将值传递给子组件,父组件值改变,子组件随着改变。子组件:通过this.$emit('update:属性名', data)来改变props属性值并更新父组件对应的值。 <template> <div @click="click2change"> 点击加一{{foo}} <my-checkbox :checked.sync="foo"></my-checkbox> </div></template><script>import Vue from 'vue';// 组件通信: props,事件,provide|inject,vuex, vuebus,。。。const myCheckbox = Vue.component('my-checkbox', { props: { value: { type: String, default: 'testvalue' }, checked: { type: Number, default: 0 } }, methods: { changeValue() { //this.checked -= 2;//会报刚刚说的错误,不能直接修改props属性值 this.$emit("update:checked", this.checked - 2); } }, template: ` <div> <div @click.stop="changeValue">checked: {{checked}}</div> </div> `});export default { name: 'list', components: { myCheckbox, }, data() { return { foo: 1 } }, methods: { click2change() { this.foo += 1; } }}</script>结果运行: ...

August 27, 2019 · 2 min · jiezi

Vue-组件通信详解

父子组件通信: props、 $parent / $children、 provide / inject 、 ref 、 $attrs / $listeners兄弟组件通信:EventBus 、 Vuex跨级组件通信: EventBus 、 Vuex 、 provide / inject 、 $attrs / $listeners父传子 子组件用 props 接收,父组件用 v-bind:prop 发送父组件<template> <div class="section"> <com-article :articles="articleList"></com-article> </div></template><script>import comArticle from "./comArticle";export default { data() { return { articleList: ["红楼梦", "西游记", "三国演义", "水浒传"] } }, components: { comArticle },}</script>子组件<template> <ul> <li v-for="(item, index) in articles" :key="index">{{item}}</li> </ul></template><script>export default { props: ["articles"]}</script>子传父 子组件用 v-on:click="" this.$emit('name', this.msg)(【有的版本名称只能小写】)发送,父组件自定义事件 v-on:name="getChildValue" 然后在 getChildValue(data){} 方法中接收父组件<template> <div class="section"> <com-article @onEmitIndex="onEmitIndex"></com-article> 【不能加括号】 <ul> <li v-for="(item, index) in articles" :key="index">{{item}}</li> </ul> </div></template><script>import comArticle from "./com2";export default { data() { return { articles:[] }; }, components: { comArticle }, methods: { onEmitIndex(data) { this.articles = data; } }}</script>子组件<template> <div> <button @click="emitIndex()">点击把articleList传给父组件</button> 【可以传参】 </div></template><script>export default { data() { return { articleList: ["红楼梦", "西游记", "三国演义", "水浒传"] }; }, methods: { emitIndex() { this.$emit("onEmitIndex", this.articleList); // } }}</script>父子传参还可以用 $parent(对象)和 $children(数组)provide / reject (上传下)父辈组件中通过 provide 来提供变量,子孙组件中通过 reject 来注入变量。父组件<template> <div> com1 是父组件 <com2></com2> </div></template><script> import com2 from './com2.vue' export default { provide: { msg: "这是父辈组件 com1 传出去的数据" }, components:{ com2 } }</script>子组件<template> <div> com2 是 com1 的子组件 {{demo}} <com3></com3> </div></template><script> import com3 from './com3.vue' export default { inject: ['msg'], data() { return { demo: this.msg } }, components: { com3 } }</script>孙组件<template> <div> com3 是 com1 的孙组件 {{msg}} </div></template><script> export default { inject: ['msg'] }</script>ref如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据 ref="xx" this.$refs.xxeventBus(事件总线,项目较大难以维护,组件都可以传) $emit(name, data)发送 $on(name, data=>{})接收 【名称小写】event-bus.jsimport Vue from 'vue'export const EventBus = new Vue()com1.vue 发送事件<button @click="additionHandle">加法器</button>import {EventBus} from './event-bus.js'data(){ return {num: 1}},additionHandle(){ EventBus.$emit('addition', {num: this.num++})com2.vue 接收事件<div>计算和: {{count}}</div>data() { return {count: 0}},mounted() { EventBus.$on('addition', param => { this.count = this.count + param.num; })}localStorage / sessionStorage因为 window.loacalStorage.setItem(key, value)、window.loacalStorage.getItem(key) 储存的是字符串,需要用 JSON.parse() / stringify() 转换可结合 vuex,实现数据持久保存和解决数据及状态混乱问题$attrs $listeners(仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用)test.vue<template> <div> test.vue <child-com1 :name="name" :age="age" :gender="gender" :height="height" title="test.vue 传出的值"></child-com1> </div></template><script>const childCom1 = () => import("./com1.vue");export default { components: { childCom1 }, data() { return { name: "zhangsan", age: "18", gender: "女", height: "158" }; }};</script><style scoped>div{ background-color: #ddd;}</style>com1.vue<template> <div class="com1"> com1 <p>name: {{name}}</p> <p>childCom1的$attrs: {{$attrs}}</p> <child-com2 v-bind="$attrs"></child-com2> </div></template><script>const childCom2 = () => import("./com2.vue");export default { components: { childCom2 }, inheritAttrs: false, // 关闭自动挂载到组件根元素上的没有在 props 声明的属性 props: { name: String }, created() { console.log(this.$attrs); // {age: "18", gender: "女", height: "158", title: "test.vue 传 com1.vue"} }};</script><style scoped>.com1{ margin: 20px; background-color: #f00;}</style>com2.vue<template> <div>com2 <p>age: {{age}}</p> <p>childCom2: {{ $attrs }}</p> </div></template><script>export default { inheritAttrs: false, props: { age: String }, created() { console.log('com2', this.$attrs); // { "name": "zhang", "gender": "女", "height": "158", "title": "程序员成长指北" } }};</script><style scoped>div{ background: #0f0; margin: 20px}</style>

June 15, 2019 · 2 min · jiezi

Vue笔记(六)——Vue组件通信&Vuex

组件通信vue本身的组件通信父==>子:父组件向子组件传参或者调用子组件的方法子==>父:子组件向父组件传参或者调用父组件的方法兄弟之间:兄弟组件之间传参或者调用方法父子通信传参 - props思路:定义子组件的属性(自定义),父组件赋值给子组件调用方法(1) - $refs思路:定义子组件的属性-ref=“a”,父组件通过:this.$refs.a.子组件方法();调用方法(2) - children思路:父组件通过:this.$children[0].子组件方法();子父通信调用方法(1) - $parent.$emit(“父组件自定义事件”);思路:父组件在组件的生命周期(mounted)用$on定义事件,子组件用this.$parent.$emit(“父组件自定义事件”);调用方法(2) - $emit(“父组件自定义事件”);思路:父组件在调用子组件时用v-on把事件传给子组件,子组件用this.$emit(“父组件自定义事件”)调用父组件传过来的事件<div id=“app”> <h1>父组件-{{this.text}}</h1> <!– 子组件 –> <child v-on:parentEvent=“changeText()"></child></div><script type=“text/javascript”> var vm = new Vue({ el:"#app”, data:{ text:’’ }, methods:{ changeText(_text){ this.text = _text; } }, components:{ ‘child’:{ template:’<p><label>子组件</label><input type=“text” v-on:input=“change” /></p>’, methods:{ change(event){ this.$emit(‘parentEvent’,event.target.value); } } } } });</script>调用方法(3) - parent思路:父组件在调用子组件时用v-on把事件传给子组件,子组件用this.$parent.父组件事件<div id=“app”> <h1>父组件-{{this.text}}</h1> <child></child></div><script type=“text/javascript”> var vm = new Vue({ el:"#app", data:{ text:’’ }, methods:{ changeText(_text){ this.text = _text; } }, components:{ ‘child’:{ template:’<p><label>子组件</label><input type=“text” v-on:input=“change” /></p>’, methods:{ change(event){ this.$parent.changeText(event.target.value); } } } } });</script>兄弟之间的通信和上面介绍的父子之间通信是一样的,因为任何连个子组件之间都会拥有一个共同的父级组件,所以兄弟组件之间的通信就是子1向父,然后父向子2,这样来达到兄弟之间组件的通信Vuex - 状态管理通信跨组件通信的一种实现方式用到就封装一个组件.js组件.js// 设置属性:stateconst state = { count = 0;}// 设置方法:mutaionsconst mutaions = { increment(_state){ _state.count += 1; }}// 执行方法const actions = { increment(_content){ _content.commit(‘increment’); }}// 暴露export default{ state, mutaions, actions}集合实例化 store.jsimport Vue from ‘Vue’;import Vuex from ‘vuex’;// 引入组件.jsimport transition from ‘./transion.js’;// 实例化对象const store = new Vue.Store({ modules:{ transition }});// 暴露对象export default store;主入口app.js实例化vuex对象// 引入vuex对象import store from ‘./vuex/store.js’;// 实例化vuex对象new Vue({ el:"#app", router, store, render:h=>h(app)});各组件之间调用1.调用参数$store.state.组建名.属性2.调用方法$store.dispatch(‘事件名’); ...

February 28, 2019 · 1 min · jiezi

vue组件之间8种组件通信方式总结

对于vue来说,组件之间的消息传递是非常重要的,下面是我对组件之间消息传递的各种方式的总结,总共有8种方式。1.props和$emit父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的。Vue.component(‘child’,{ data(){ return { mymessage:this.message } }, template: &lt;div&gt; &lt;input type="text" v-model="mymessage" @input="passData(mymessage)"&gt; &lt;/div&gt; , props:[‘message’],//得到父组件传递过来的数据 methods:{ passData(val){ //触发父组件中的事件 this.$emit(‘getChildData’,val) } } }) Vue.component(‘parent’,{ template: &lt;div&gt; &lt;p&gt;this is parent compoent!&lt;/p&gt; &lt;child :message="message" v-on:getChildData="getChildData"&gt;&lt;/child&gt; &lt;/div&gt; , data(){ return { message:‘hello’ } }, methods:{ //执行子组件触发的事件 getChildData(val){ console.log(val) } } }) var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;parent&gt;&lt;/parent&gt; &lt;/div&gt; })在上面的例子中,有父组件parent和子组件child。 1).父组件传递了message数据给子组件,并且通过v-on绑定了一个getChildData事件来监听子组件的触发事件; 2).子组件通过props得到相关的message数据,最后通过this.$emit触发了getChildData事件。2.$attrs和$listeners第一种方式处理父子组件之间的数据传输有一个问题:如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢? 如果采用第一种方法,我们必须让组件A通过prop传递消息给组件B,组件B在通过prop传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。Vue 2.4开始提供了$attrs和$listeners来解决这个问题,能够让组件A之间传递消息给组件C。Vue.component(‘C’,{ template: &lt;div&gt; &lt;input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"&gt; &lt;/div&gt; , methods:{ passCData(val){ //触发父组件A中的事件 this.$emit(‘getCData’,val) } } }) Vue.component(‘B’,{ data(){ return { mymessage:this.message } }, template: &lt;div&gt; &lt;input type="text" v-model="mymessage" @input="passData(mymessage)"&gt; &lt;!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 --&gt; &lt;!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) --&gt; &lt;C v-bind="$attrs" v-on="$listeners"&gt;&lt;/C&gt; &lt;/div&gt; , props:[‘message’],//得到父组件传递过来的数据 methods:{ passData(val){ //触发父组件中的事件 this.$emit(‘getChildData’,val) } } }) Vue.component(‘A’,{ template: &lt;div&gt; &lt;p&gt;this is parent compoent!&lt;/p&gt; &lt;B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"&gt;&lt;/B&gt; &lt;/div&gt; , data(){ return { message:‘hello’, messagec:‘hello c’ //传递给c组件的数据 } }, methods:{ getChildData(val){ console.log(‘这是来自B组件的数据’) }, //执行C子组件触发的事件 getCData(val){ console.log(“这是来自C组件的数据:"+val) } } }) var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;A&gt;&lt;/A&gt; &lt;/div&gt; })3.中央事件总线上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系呢?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。Vue.component(‘brother1’,{ data(){ return { mymessage:‘hello brother1’ } }, template: &lt;div&gt; &lt;p&gt;this is brother1 compoent!&lt;/p&gt; &lt;input type="text" v-model="mymessage" @input="passData(mymessage)"&gt; &lt;/div&gt; , methods:{ passData(val){ //触发全局事件globalEvent bus.$emit(‘globalEvent’,val) } } }) Vue.component(‘brother2’,{ template: &lt;div&gt; &lt;p&gt;this is brother2 compoent!&lt;/p&gt; &lt;p&gt;brother1传递过来的数据:{{brothermessage}}&lt;/p&gt; &lt;/div&gt; , data(){ return { mymessage:‘hello brother2’, brothermessage:’’ } }, mounted(){ //绑定全局事件globalEvent bus.$on(‘globalEvent’,(val)=>{ this.brothermessage=val; }) } }) //中央事件总线 var bus=new Vue(); var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;brother1&gt;&lt;/brother1&gt; &lt;brother2&gt;&lt;/brother2&gt; &lt;/div&gt; })4.provide和inject父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。Vue.component(‘child’,{ inject:[‘for’],//得到父组件传递过来的数据 data(){ return { mymessage:this.for } }, template: &lt;div&gt; &lt;input type="tet" v-model="mymessage"&gt; &lt;/div&gt; }) Vue.component('parent',{ template: <div> <p>this is parent compoent!</p> <child></child> </div> , provide:{ for:'test' }, data(){ return { message:'hello' } } }) var app=new Vue({ el:'#app', template: <div> <parent></parent> </div> })5.v-model父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值Vue.component('child',{ props:{ value:String, //v-model会自动传递一个字段为value的prop属性 }, data(){ return { mymessage:this.value } }, methods:{ changeValue(){ this.$emit('input',this.mymessage);//通过如此调用可以改变父组件上v-model绑定的值 } }, template: <div> <input type=“text” v-model=“mymessage” @change=“changeValue”> </div> }) Vue.component(‘parent’,{ template: &lt;div&gt; &lt;p&gt;this is parent compoent!&lt;/p&gt; &lt;p&gt;{{message}}&lt;/p&gt; &lt;child v-model="message"&gt;&lt;/child&gt; &lt;/div&gt; , data(){ return { message:‘hello’ } } }) var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;parent&gt;&lt;/parent&gt; &lt;/div&gt; })6.$parent和$childrenVue.component(‘child’,{ props:{ value:String, //v-model会自动传递一个字段为value的prop属性 }, data(){ return { mymessage:this.value } }, methods:{ changeValue(){ this.$parent.message = this.mymessage;//通过如此调用可以改变父组件的值 } }, template: &lt;div&gt; &lt;input type="text" v-model="mymessage" @change="changeValue"&gt; &lt;/div&gt; }) Vue.component('parent',{ template: <div> <p>this is parent compoent!</p> <button @click=“changeChildValue”>test</button > <child></child> </div> , methods:{ changeChildValue(){ this.$children[0].mymessage = 'hello'; } }, data(){ return { message:'hello' } } }) var app=new Vue({ el:'#app', template: <div> <parent></parent> </div> ` })7.boradcast和dispatchvue1.0中提供了这种方式,但vue2.0中没有,但很多开源软件都自己封装了这种方式,比如min ui、element ui和iview等。 比如如下代码,一般都作为一个mixins去使用, broadcast是向特定的父组件,触发事件,dispatch是向特定的子组件触发事件,本质上这种方式还是on和on和emit的封装,但在一些基础组件中却很实用。function broadcast(componentName, eventName, params) { this.$children.forEach(child => { var name = child.$options.componentName; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat(params)); } });}export default { methods: { dispatch(componentName, eventName, params) { var parent = this.$parent; var name = parent.$options.componentName; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.componentName; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } }};8.vuex处理组件之间的数据交互如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 详情可参考:https://vuex.vuejs.org/zh-cn/ ...

February 28, 2019 · 3 min · jiezi

vuejs组件通信精髓归纳

组件的分类常规页面组件,由 vue-router 产生的每个页面,它本质上也是一个组件(.vue),主要承载当前页面的 HTML 结构,会包含数据获取、数据整理、数据可视化等常规业务。功能性抽象组件,不包含业务,独立、具体功能的基础组件,比如日期选择器、弹窗警告等。这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。业务组件,它不像第二类独立组件只包含某个功能,而是在业务中被多个页面复用的,它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。组件的关系父子组件父子关系即是组件 A 在它的模板中使用了组件 B,那么组件 A 就是父组件,组件 B 就是子组件。// 注册一个子组件Vue.component(‘child’, { data: function(){ return { text: ‘我是father的子组件!’ } }, template: ‘<span>{{ text }}</span>’})// 注册一个父组件Vue.component(‘father’, { template: ‘<div><child></child></div>’ // 在模板中使用了child组件})兄弟组件两个组件互不引用,则为兄弟组件。Vue.component(‘brother1’, { template: ‘<div>我是大哥</div>’})Vue.component(‘brother2’, { template: ‘<div>我是小弟</div>’})使用组件的时候:<div id=“app”> <brother1></brother1> <brother2></brother2></div>跨级组件就是在父子关系中,中间跨了很多个层级组件的构成一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API。属性 propprop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。插槽 slot插槽 slot,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:<alert-box> Something bad happened.</alert-box>可能会渲染出这样的东西:Error!Something bad happended.幸好,Vue 自定义的 <slot> 元素让这变得非常简单:Vue.component(‘alert-box’, { template: &lt;div class="demo-alert-box"&gt; &lt;strong&gt;Error!&lt;/strong&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/div&gt; })如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!自定义事件 event两种写法:在组件内部自定义事件event<template> <button @click=“handleClick”> <slot></slot> </button></template><script> export default { methods: { handleClick (event) { this.$emit(‘on-click’, event); } } }</script>通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:<i-button @on-click=“handleClick”></i-button>用事件修饰符 .native直接在父级声明所以上面的示例也可以这样写:<i-button @click.native=“handleClick”></i-button>如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click,但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。组件的通信ref和$parent和$childrenVue.js 内置的通信手段一般有两种:ref:给元素或组件注册引用信息;$parent / $children:访问父 / 子实例。用 ref 来访问组件(部分代码省略):// component-aexport default { data () { return { title: ‘Vue.js’ } }, methods: { sayHello () { window.alert(‘Hello’); } }}<template> <component-a ref=“comA”></component-a></template><script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 弹窗 } }</script>$parent 和 $children 类似,也是基于当前上下文访问父组件或全部子组件的。这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:// parent.vue<component-a></component-a><component-b></component-b><component-b></component-b>我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。provide / inject一种无依赖的组件通信方法:Vue.js 内置的 provide / inject 接口provide / inject 是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件:// A.vueexport default { provide: { name: ‘Aresn’ }}// B.vueexport default { inject: [’name’], mounted () { console.log(this.name); // Aresn }}需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。只要一个组件使用了 provide 向下提供数据,那其下所有的子组件都可以通过 inject 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:父组件向子组件(支持跨级)传递数据;子组件向父组件(支持跨级)传递数据。这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法 dispatch 和 broadcast。$attrs和$listeners如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢? Vue 2.4开始提供了$attrs和$listeners来解决这个问题,能够让组件A之间传递消息给组件C。Vue.component(‘C’,{ template: &lt;div&gt; &lt;input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"&gt; &lt;/div&gt; , methods:{ passCData(val){ //触发父组件A中的事件 this.$emit(‘getCData’,val) } } }) Vue.component(‘B’,{ data(){ return { mymessage:this.message } }, template: &lt;div&gt; &lt;input type="text" v-model="mymessage" @input="passData(mymessage)"&gt; &lt;!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 --&gt; &lt;!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) --&gt; &lt;C v-bind="$attrs" v-on="$listeners"&gt;&lt;/C&gt; &lt;/div&gt; , props:[‘message’],//得到父组件传递过来的数据 methods:{ passData(val){ //触发父组件中的事件 this.$emit(‘getChildData’,val) } } }) Vue.component(‘A’,{ template: &lt;div&gt; &lt;p&gt;this is parent compoent!&lt;/p&gt; &lt;B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"&gt;&lt;/B&gt; &lt;/div&gt; , data(){ return { message:‘hello’, messagec:‘hello c’ //传递给c组件的数据 } }, methods:{ getChildData(val){ console.log(‘这是来自B组件的数据’) }, //执行C子组件触发的事件 getCData(val){ console.log(“这是来自C组件的数据:"+val) } } }) var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;A&gt;&lt;/A&gt; &lt;/div&gt; })派发与广播——自行实现 dispatch 和 broadcast 方法要实现的 dispatch 和 broadcast 方法,将具有以下功能:在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。// 部分代码省略import Emitter from ‘../mixins/emitter.js’export default { mixins: [ Emitter ], methods: { handleDispatch () { this.dispatch(); // ① }, handleBroadcast () { this.broadcast(); // ② } }} //emitter.js 的代码:function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } });}export default { methods: { dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } }};因为是用作 mixins 导入,所以在 methods 里定义的 dispatch 和 broadcast 方法会被混合到组件里,自然就可以用 this.dispatch 和 this.broadcast 来使用。这两个方法都接收了三个参数,第一个是组件的 name 值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:<!– A.vue –><template> <button @click=“handleClick”>触发事件</button></template><script> import Emitter from ‘../mixins/emitter.js’; export default { name: ‘componentA’, mixins: [ Emitter ], methods: { handleClick () { this.broadcast(‘componentB’, ‘on-message’, ‘Hello Vue.js’); } } }</script>// B.vueexport default { name: ‘componentB’, created () { this.$on(‘on-message’, this.showMessage); }, methods: { showMessage (text) { window.alert(text); } }}同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。以上就是自行实现的 dispatch 和 broadcast 方法。找到任意组件实例——findComponents 系列方法它适用于以下场景:由一个组件,向上找到最近的指定组件;由一个组件,向上找到所有的指定组件;由一个组件,向下找到最近的指定组件;由一个组件,向下找到所有指定的组件;由一个组件,找到指定组件的兄弟组件。5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。向上找到最近的指定组件——findComponentUpward// 由一个组件,向上找到最近的指定组件function findComponentUpward (context, componentName) { let parent = context.$parent; let name = parent.$options.name; while (parent && (!name || [componentName].indexOf(name) < 0)) { parent = parent.$parent; if (parent) name = parent.$options.name; } return parent;}export { findComponentUpward };比如下面的示例,有组件 A 和组件 B,A 是 B 的父组件,在 B 中获取和调用 A 中的数据和方法:<!– component-a.vue –><template> <div> 组件 A <component-b></component-b> </div></template><script> import componentB from ‘./component-b.vue’; export default { name: ‘componentA’, components: { componentB }, data () { return { name: ‘Aresn’ } }, methods: { sayHello () { console.log(‘Hello, Vue.js’); } } }</script><!– component-b.vue –><template> <div> 组件 B </div></template><script> import { findComponentUpward } from ‘../utils/assist.js’; export default { name: ‘componentB’, mounted () { const comA = findComponentUpward(this, ‘componentA’); if (comA) { console.log(comA.name); // Aresn comA.sayHello(); // Hello, Vue.js } } }</script>向上找到所有的指定组件——findComponentsUpward// 由一个组件,向上找到所有的指定组件function findComponentsUpward (context, componentName) { let parents = []; const parent = context.$parent; if (parent) { if (parent.$options.name === componentName) parents.push(parent); return parents.concat(findComponentsUpward(parent, componentName)); } else { return []; }}export { findComponentsUpward };向下找到最近的指定组件——findComponentDownward// 由一个组件,向下找到最近的指定组件function findComponentDownward (context, componentName) { const childrens = context.$children; let children = null; if (childrens.length) { for (const child of childrens) { const name = child.$options.name; if (name === componentName) { children = child; break; } else { children = findComponentDownward(child, componentName); if (children) break; } } } return children;}export { findComponentDownward };向下找到所有指定的组件——findComponentsDownward// 由一个组件,向下找到所有指定的组件function findComponentsDownward (context, componentName) { return context.$children.reduce((components, child) => { if (child.$options.name === componentName) components.push(child); const foundChilds = findComponentsDownward(child, componentName); return components.concat(foundChilds); }, []);}export { findComponentsDownward };找到指定组件的兄弟组件——findBrothersComponents// 由一个组件,找到指定组件的兄弟组件function findBrothersComponents (context, componentName, exceptMe = true) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName; }); let index = res.findIndex(item => item._uid === context._uid); if (exceptMe) res.splice(index, 1); return res;}export { findBrothersComponents };相比其它 4 个函数,findBrothersComponents 多了一个参数 exceptMe,是否把本身除外,默认是 true。寻找兄弟组件的方法,是先获取 context.$parent.$children,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数 exceptMe。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid,这个 _uid 是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。Event Bus有时候两个组件之间需要进行通信,但是它们彼此不是父子组件的关系。在一些简单场景,你可以使用一个空的 Vue 实例作为一个事件总线中心(central event bus): //中央事件总线 var bus=new Vue(); var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;brother1&gt;&lt;/brother1&gt; &lt;brother2&gt;&lt;/brother2&gt; &lt;/div&gt; })// 在组件 brother1 的 methods 方法中触发事件bus.$emit(‘say-hello’, ‘world’)// 在组件 brother2 的 created 钩子函数中监听事件bus.$on(‘say-hello’, function (arg) { console.log(‘hello ’ + arg); // hello world})vuex处理组件之间的数据交互如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 详情可参考:https://vuex.vuejs.org/zh-cn/参考 vue组件之间8种组件通信方式总结参考 https://github.com/iview/ivie…参考 https://github.com/iview/ivie… ...

February 22, 2019 · 5 min · jiezi

vuejs组件通信精髓归纳

组件的分类常规页面组件,由 vue-router 产生的每个页面,它本质上也是一个组件(.vue),主要承载当前页面的 HTML 结构,会包含数据获取、数据整理、数据可视化等常规业务。功能性抽象组件,不包含业务,独立、具体功能的基础组件,比如日期选择器、模态框等。这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。业务组件,它不像第二类独立组件只包含某个功能,而是在业务中被多个页面复用的,它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。组件的构成一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API。属性 propprop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。插槽 slot插槽 slot,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:<alert-box> Something bad happened.</alert-box>可能会渲染出这样的东西:Error!Something bad happended.幸好,Vue 自定义的 <slot> 元素让这变得非常简单:Vue.component(‘alert-box’, { template: &lt;div class="demo-alert-box"&gt; &lt;strong&gt;Error!&lt;/strong&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/div&gt; })如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!自定义事件 event两种写法:在组件内部自定义事件event<template> <button @click=“handleClick”> <slot></slot> </button></template><script> export default { methods: { handleClick (event) { this.$emit(‘on-click’, event); } } }</script>通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:<i-button @on-click=“handleClick”></i-button>用事件修饰符 .native直接在父级声明所以上面的示例也可以这样写:<i-button @click.native=“handleClick”></i-button>如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click,但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。组件的通信ref和$parent和$childrenVue.js 内置的通信手段一般有两种:ref:给元素或组件注册引用信息;$parent / $children:访问父 / 子实例。用 ref 来访问组件(部分代码省略):// component-aexport default { data () { return { title: ‘Vue.js’ } }, methods: { sayHello () { window.alert(‘Hello’); } }}<template> <component-a ref=“comA”></component-a></template><script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 弹窗 } }</script>$parent 和 $children 类似,也是基于当前上下文访问父组件或全部子组件的。这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:// parent.vue<component-a></component-a><component-b></component-b><component-b></component-b>我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。provide / inject一种无依赖的组件通信方法:Vue.js 内置的 provide / inject 接口provide / inject 是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件:// A.vueexport default { provide: { name: ‘Aresn’ }}// B.vueexport default { inject: [’name’], mounted () { console.log(this.name); // Aresn }}需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。只要一个组件使用了 provide 向下提供数据,那其下所有的子组件都可以通过 inject 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:父组件向子组件(支持跨级)传递数据;子组件向父组件(支持跨级)传递数据。这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法 dispatch 和 broadcast。派发与广播——自行实现 dispatch 和 broadcast 方法要实现的 dispatch 和 broadcast 方法,将具有以下功能:在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。// 部分代码省略import Emitter from ‘../mixins/emitter.js’export default { mixins: [ Emitter ], methods: { handleDispatch () { this.dispatch(); // ① }, handleBroadcast () { this.broadcast(); // ② } }} //emitter.js 的代码:function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } });}export default { methods: { dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } }};因为是用作 mixins 导入,所以在 methods 里定义的 dispatch 和 broadcast 方法会被混合到组件里,自然就可以用 this.dispatch 和 this.broadcast 来使用。这两个方法都接收了三个参数,第一个是组件的 name 值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:<!– A.vue –><template> <button @click=“handleClick”>触发事件</button></template><script> import Emitter from ‘../mixins/emitter.js’; export default { name: ‘componentA’, mixins: [ Emitter ], methods: { handleClick () { this.broadcast(‘componentB’, ‘on-message’, ‘Hello Vue.js’); } } }</script>// B.vueexport default { name: ‘componentB’, created () { this.$on(‘on-message’, this.showMessage); }, methods: { showMessage (text) { window.alert(text); } }}同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。以上就是自行实现的 dispatch 和 broadcast 方法。找到任意组件实例——findComponents 系列方法它适用于以下场景:由一个组件,向上找到最近的指定组件;由一个组件,向上找到所有的指定组件;由一个组件,向下找到最近的指定组件;由一个组件,向下找到所有指定的组件;由一个组件,找到指定组件的兄弟组件。5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。向上找到最近的指定组件——findComponentUpward// 由一个组件,向上找到最近的指定组件function findComponentUpward (context, componentName) { let parent = context.$parent; let name = parent.$options.name; while (parent && (!name || [componentName].indexOf(name) < 0)) { parent = parent.$parent; if (parent) name = parent.$options.name; } return parent;}export { findComponentUpward };比如下面的示例,有组件 A 和组件 B,A 是 B 的父组件,在 B 中获取和调用 A 中的数据和方法:<!– component-a.vue –><template> <div> 组件 A <component-b></component-b> </div></template><script> import componentB from ‘./component-b.vue’; export default { name: ‘componentA’, components: { componentB }, data () { return { name: ‘Aresn’ } }, methods: { sayHello () { console.log(‘Hello, Vue.js’); } } }</script><!– component-b.vue –><template> <div> 组件 B </div></template><script> import { findComponentUpward } from ‘../utils/assist.js’; export default { name: ‘componentB’, mounted () { const comA = findComponentUpward(this, ‘componentA’); if (comA) { console.log(comA.name); // Aresn comA.sayHello(); // Hello, Vue.js } } }</script>向上找到所有的指定组件——findComponentsUpward// 由一个组件,向上找到所有的指定组件function findComponentsUpward (context, componentName) { let parents = []; const parent = context.$parent; if (parent) { if (parent.$options.name === componentName) parents.push(parent); return parents.concat(findComponentsUpward(parent, componentName)); } else { return []; }}export { findComponentsUpward };向下找到最近的指定组件——findComponentDownward// 由一个组件,向下找到最近的指定组件function findComponentDownward (context, componentName) { const childrens = context.$children; let children = null; if (childrens.length) { for (const child of childrens) { const name = child.$options.name; if (name === componentName) { children = child; break; } else { children = findComponentDownward(child, componentName); if (children) break; } } } return children;}export { findComponentDownward };向下找到所有指定的组件——findComponentsDownward// 由一个组件,向下找到所有指定的组件function findComponentsDownward (context, componentName) { return context.$children.reduce((components, child) => { if (child.$options.name === componentName) components.push(child); const foundChilds = findComponentsDownward(child, componentName); return components.concat(foundChilds); }, []);}export { findComponentsDownward };找到指定组件的兄弟组件——findBrothersComponents// 由一个组件,找到指定组件的兄弟组件function findBrothersComponents (context, componentName, exceptMe = true) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName; }); let index = res.findIndex(item => item._uid === context._uid); if (exceptMe) res.splice(index, 1); return res;}export { findBrothersComponents };相比其它 4 个函数,findBrothersComponents 多了一个参数 exceptMe,是否把本身除外,默认是 true。寻找兄弟组件的方法,是先获取 context.$parent.$children,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数 exceptMe。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid,这个 _uid 是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。参考Vuejs组件精讲 ...

February 17, 2019 · 4 min · jiezi

Vue组件通信的几种方式

父组件通过 Prop 向子组件传递数据这个数据流是单向的。数据流向是从父组件传到子组件。也就是说,父级 prop 的更新会向下流动到子组件中,但是反过来则不行。现在我们需要写一个博文组件,展示博文的标题和内容。标题和内容这些数据是从父组件获得的。第一步,定义一个组件,这里用注册全局组件的方式,子组件用prop接收来自父组件的数据:Vue.component(‘blog-post’, { props: [‘post’], template: &lt;div class="blog-post"&gt; &lt;h2&gt;{{ post.title }}&lt;/h2&gt; &lt;div v-html="post.content"&gt;&lt;/div&gt; &lt;/div&gt; });第二步,初始化一个Vue实例并挂载到对应的HTML结构:// html<div id=“app”> <blog-post :post=“post” v-for="(post, index) in postArr" :key=“index”></blog-post></div>// jsnew Vue({ el: “#app”, data: { postArr: [ { title: ‘My name is Judy’, content: ‘something here ’ }, { title: ‘My job is coding’, content: ‘something here ’ }, { title: ‘I like coding’, content: ‘something here ’ } ] }});demo:父组件通过 Prop 向子组件传递数据子组件通过事件向父级组件发送消息在我们开发 <blog-post> 组件时,它的一些功能可能要求我们和父级组件进行沟通。例如我们可能会引入一个可访问性的功能来放大博文的字号,同时让其他部分的博文保持默认的字号。还是刚才那个demo,在其父组件中,我们可以通过给每一条博文的数据添加一个 postFontSize 数据属性来控制每篇博文字体的大小。// jsnew Vue({ el: “#app”, data: { postArr: [ { postFontSize: 1, title: ‘My name is Judy’, content: ‘something here ’ }, { postFontSize: 1, title: ‘My job is coding’, content: ‘something here ’ }, { postFontSize: 1, title: ‘I like coding’, content: ‘something here ’ } ] }});现在我们需要给子组件添加一个按钮。当点击这个按钮时,我们需要告诉父级组件放大所有博文的文本。刚好 Vue 实例提供了一个自定义事件的系统来解决这个问题。我们可以调用内建的 $emit 方法并传入事件的名字,来向父级组件触发一个事件。改写一下子组件:Vue.component(‘blog-post’, { props: [‘post’], template: &lt;div class="blog-post"&gt; &lt;h2&gt;{{ post.title }}&lt;/h2&gt; &lt;button @click="$emit('enlarge-text')"&gt; Enlarge text &lt;/button&gt; &lt;div v-html="post.content"&gt;&lt;/div&gt; &lt;/div&gt; });然后我们可以用 v-on 在博文组件上监听这个事件,就像监听一个原生 DOM 事件一样:<div id=“app”> <blog-post @enlarge-text=“post.postFontSize += 0.1” :post=“post” v-for="(post, index) in post_arr" :key=“index” :style="{ fontSize: post.postFontSize + ’em’ }" ></blog-post> </div>demo:子组件通过事件向父级组件发送消息以上为常用的父组件向子组件传递数据、子组件通过事件向父级组件发送消息的两种方式。$parent访问父级组件实例$parent 属性可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式,实际上这不是一种数据传递,而是子组件主动发起的数据查找。但是Vue并不推荐这么做。在绝大多数情况下,触达父级组件会使得我们的应用更难调试和理解,尤其是我们变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。在一些可能适当的时候,我们需要特别地共享一些组件库,就可以考虑用$parent。Vue官网称这些情况为【边界情况】。假设有一个Google地图组件:<google-map> <google-map-markers v-bind:places=“iceCreamShops”></google-map-markers></google-map>这个 <google-map> 组件可以定义一个 map 属性,所有的子组件都需要访问它。在这种情况下 <google-map-markers> 可以通过类似 this.$parent.getMap 的方式访问那个地图,以便为其添加一组标记。点击这里查看官方文档给出的demoref 特性访问子组件实例或子元素尽管存在 prop 和事件,有的时候仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,我们可以通过 ref 特性为这个子组件赋予一个 ID 引用。例如:<base-input ref=“usernameInput”></base-input>现在在已经定义了这个 ref 的组件里就可以使用:this.$refs.usernameInput来访问这个 <base-input> 实例,以便不时之需。另外,vm.$children也可以访问当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。当我们需要访问子组件或者子元素,推荐使用 ref 而不是 $children。因为 ref 能帮我们访问到的是任意子组件实例或者子元素,$children 只能访问到直接子组件。VuexVuex 是一个专为 Vue.js 应用程序开发的状态管理模式。什么意思呢?在实际开发中,我们会遇到有一处或者多处需要被多个实例间共享的状态,Vuex就是为我们提供了这样一个模式。所以我的理解是Vuex不仅仅是适用于父子组件的通信了,它适用于所有组件共享某些必要的数据状态。关于Vuex的使用我们这里不详细展开讨论了,贴一个Vuex的文档地址。一些补充前面我们提到google地图组件,类似这样的:<google-map> <google-map-region v-bind:shape=“cityBoundaries”> <google-map-markers v-bind:places=“iceCreamShops”></google-map-markers> </google-map-region></google-map>在这个组件里,所有 <google-map> 的后代都需要访问一个 getMap 方法,以便知道要跟那个地图进行交互。不幸的是,使用 $parent 属性无法很好的扩展到更深层级的嵌套组件上,也就是说,只有<google-map-region>能通过 $parent 访问到它的直接父组件<google-map>的 getMap 方法。这种情况可以用依赖注入 provide 和 inject 解决。provide 选项允许我们指定我们想要提供给后代组件的数据/方法。在这个例子中,就是 <google-map> 内部的 getMap 方法:provide: function () { return { getMap: this.getMap }}然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的属性:inject: [‘getMap’]点击这里查看官方文档的demo相比 $parent 来说,这个用法可以让我们在任意后代组件中访问 getMap,而不需要暴露整个 <google-map> 实例。这允许我们更好的持续研发该组件,而不需要担心我们可能会改变/移除一些子组件依赖的东西。同时这些组件之间的接口是始终明确定义的,就和 props 一样。实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:祖先组件不需要知道哪些后代组件使用它提供的属性后代组件不需要知道被注入的属性来自哪里我看了很多篇总结Vue父子组件通信方式的博客都没有提到依赖注入,不过我觉得依赖注入也实现了通信——由父组件通过 provide 把数据暴露出去,子组件通过 inject 接收数据。以上就是我阅读官方文档总结出来的Vue组件通信的几种方式,大部分内容和demo都来源于官网文档,了解更多可以阅读文档:Vue文档地址。文章全部内容仅代表个人观点,欢迎批评或者与我讨论。感谢你的阅读。 ...

December 27, 2018 · 2 min · jiezi