Vue组件间通信

150 阅读1分钟

组件

Vue中最强大的莫过于组件了吧,但是组件中的作用域是独立的,所以组件的数据没办法互相引用。为了解决这一问题,组件间通信应运而生。

父向子传值

在子组件中写props来接收数据

//父组件
<template>
  <div>
      <children :menus="menus"/>
  </div>
</template>

<script>
import Children from '@/components/children.vue';
export default {
  "children": Children,
  props: {},
  data() {
    return {
        menus: ['首页','菜单一','菜单二']
    };
  },
  watch: {},
  computed: {},
  methods: {},
  created() {},
  mounted() {}
};
</script>
<style lang="less" scoped>

</style>


//子组件
<template>
  <div>
      <ul>
          <li v-for="menu in menus">{{menu}}</li>
      </ul>
  </div>
</template>

<script>

export default {
  components: {},
  props: {
  //父组件标签名字
      menus: {
          type: Array,
          required: true
      }
  },
  data() {
    return {
    }
    
  },
  watch: {},
  computed: {},
  methods: {},
  created() {},
  mounted() {}
};
</script>
<style lang="less" scoped>

</style>

父组件通过props向下传递给子组件,组件的数据形式有三种:data,props,computed

子向父传值

子组件传值给父组件,需要通过事件

//父组件
<template>
  <div>
  //通过自定义事件接收子组件传的值
      <children @getMsg="getData"/>
     <p>{{msg}}</p>
  </div>
</template>

<script>
import Children from '@/components/children.vue';
export default {
  "children": Children,
  props: {},
  data() {
    return {
       msg: ""
    };
  },
  watch: {},
  computed: {},
  methods: {
    getData(data) {
      this.msg = data
    }
  },
  created() {},
  mounted() {}
};
</script>
<style lang="less" scoped>

</style>

//子组件
<template>
  <div>
    <button @click="sendMsg">子组件传值给父组件</button>
  </div>
</template>

<script>

export default {
  components: {},

  data() {
    return {
      msg: '给父组件传的值'
    }
    
  },
  watch: {},
  computed: {},
  methods: {
    sendMsg() {
      this.$emit('getMsg',this.msg);
    }
  },
  created() {},
  mounted() {}
};
</script>
<style lang="less" scoped>

</style>

子组件通过事件给父组件发消息,把自己的数据发送给子组件

通过VueX传值

vueX的数据是响应式的,不会被保存,刷新就会没有,所以要保存数据要拷贝一份到localStorage中,刷新之后,在localStorage中取出来替换state

let defaultCity = "上海"
try {   // 用户关闭了本地存储功能,此时在外层加个try...catch
  if (!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // 数据改变的时候把数据拷贝一份保存到localStorage里面
      } catch (e) {}
    }
  }
})

由于VueX中保存的是数组,而localStorage只支持字符串,所以要用JSON转换

JSON.stringify(state.defaultCity);   // array -> string
JSON.parse(window.localStorage.getItem("defaultCity"));    // string -> array 

EventBus(非父子组件)

新建一个.js文件或者在main.js中注册

//新建.js文件
import Vue from 'vue'
export default new Vue()
//main.js中注册
Vue.prototype.$EventBus = new Vue()

//发送方
	<template>
		 <div class="add-task" :class="{'ishide':isAdding}"  @click="addtask()">
		<i class="fa fa-plus-circle" aria-hidden="true"></i>
		添加任务
		</div>
	</template>
	<script>
	import Bus from '@/bus'
	export default {
	methods: {
	props: ['index'],
	data () {
 		return {
   			isAdding: false
 		}
	},
 	addtask () {
   	this.isAdding = true
   	Bus.$emit('adding-task', this.isAdding, this.index)  // 这里触发的事件是'adding-task',
   	传递了两个参数,分别是this.isAdding和this.index
   	this.$emit('addtask')
 			}
		}
	}

//接收方
	export default{
	
	created () {
	// 这里使用on监听了adding-task事件,接收到两个参数。所以一旦上面的组件中的adding-task事件触发,这里就会监听到。
	Bus.$on('adding-task', (state, index) => {
  		if (this.index === index) {
    		this.isShow = state
  		}
		})
		}
	}

因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。

provide 与 inject

假设有两个组件,B组件是A的子组件

//B组件
export default {
inject:['age'],
mouted() {
console.log(this.age) //18
  }
}

//A组件
export default {
	provide: {
    	age: 18
    }
}

ref

ref用于给元素或子组件注册引用信息,引用信息将会注册在父组件的ref上,父组件通过ref上,父组件通过ref获取在子组件上定义的属性和方法,通过调用方法向子组件传递数据

父组件向子组件传值

//父组件
<template>
    <div>
        <Child ref="child"></Child>
    </div>
</template>

<script>
import Child from "@/components/Child"
export default {
  data() {
     return {
        msg: "父组件传给子组件的值"
     }
  },
  mounted() {
  	 //父组件通过ref属性调用子组件的方法
  	 this.$refs.child.getMsg(this.msg)
  },
  components: {
     Child
  }
}
</script>

<style scoped>
</style>


//子组件
<template>
    <div>
        {{msg}} 
    </div>
</template>

<script>
export default {
  data() {
     return {
    	msg: "
     }
  },
  methods: {
  	 //子组件获取父组件值的方法
     getMsg(val) {
        this.msg = val
     }
  }
}
</script>

<style scoped>
</style>

$attrs和$listeners

  • attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind="attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
  • listeners:包含了父作用域中的(不含.native修饰器的)von事件监听器。它可以通过von="listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners" 传入内部组件
// index.vue
<template>
  <div>
    <h2>demo</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端"
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>
// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  props: {
    foo: String // foo作为props属性绑定
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端" }
  }
};
</script>

// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // {"coo": "CSS", "doo": "Vue", "title": "前端" }
  }
};
</script>

// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>

attrsattrs与listeners 是两个对象,attrs里存放的是父组件中绑定的非Props属性,attrs 里存放的是父组件中绑定的非 Props 属性,listeners里存放的是父组件中绑定的非原生事件。

$parent和$children

父向子传值用$parent

//父组件
<template>
    <div>
        <Child ref="child"></Child>
    </div>
</template>

<script>
import Child from "@/components/Child"
export default {
  data() {
     return {
        msg: "父组件传给子组件的值"
     }
  },
  mounted() {
  	 //父组件通过$children[0]访问对应子组件
  	 this.$children[0].msg = this.msg
  },
  components: {
     Child
  }
}
</script>

<style scoped>
</style>


//子组件
<template>
    <div>
        {{msg}} 
    </div>
</template>

<script>
export default {
  data() {
     return {
        msg: ""
     }
  }
}
</script>

<style scoped>
</style>

子向父传值$parent

//子组件
<template>
    <div>
        <button @click="sendMsg">子组件传值给父组件</button>
    </div>
</template>

<script>
export default {
  data() {
     return {
        msg: "子组件传给父组件的值"
     }
  },
  methods: {
  	 //子组件通过$parent访问父组件
     sendMsg() {
        this.$parent.msg = this.msg
     }
  }
}
</script>

<style scoped>
</style>

//父组件
<template>
    <div>
        <Child></Child>
        <p>{{msg}}</p>
    </div>
</template>

<script>
import Child from "@/components/Child";
export default {
  data() {
     return {
        msg: ""
     }
  }
  components: {
     Child
  }
}
</script>

<style scoped>
</style>

父向子传值($children)

children为当前组件的直接子组件,是一个无序的数组,父组件通过 children 访问子组件并传递数据,$ children 并不保证顺序,也不是响应式的,如果能清楚的知道子组件的顺序,可以使用下标来操作对应的子组件

//父组件
<template>
    <div>
        <Child ref="child"></Child>
    </div>
</template>

<script>
import Child from "@/components/Child"
export default {
  data() {
     return {
        msg: "父组件传给子组件的值"
     }
  },
  mounted() {
  	 //父组件通过$children[0]访问对应子组件
  	 this.$children[0].msg = this.msg
  },
  components: {
     Child
  }
}
</script>

<style scoped>
</style>

//子组件
<template>
    <div>
        {{msg}} 
    </div>
</template>

<script>
export default {
  data() {
     return {
        msg: ""
     }
  }
}
</script>

<style scoped>
</style>

作用域插槽

//父组件
<template>
    <div>
      <child :propName="items"> <!--传值到子组件-->
      <!--写法1 -->
      <li
         <!--作用域插槽也可以具名 绑定slot name="slotName"-->
        slot="slotName"       
          <!--把子组件插槽看作一个对象, 赋给scopeName-->
        slot-scope="scopeName">
         <!-- dos="item.do" (子组件中)-->
         {{scopeName.dos}}
       </li>
     </child>
 
     <!--写法2 es6 的解构写法 推荐!!!-->
     <child :propName="items">
       <li slot="slotName" slot-scope="{item}">
         {{item.do}}
       </li>
     </child>21   </div>
 </template>
 <script>
   import child from "./components/child.vue"
   export default{
     components:{child},
     data(){
       return{
         items:[
           {do:'play'},
           {do:'eat'},
           {do:'sleep'},
          {do:'play'},
           {do:'eat'},
           {do:'sleep'}
         ]
       }
     }
   }
 </script>

//子组件
<template>
    <ul>
      <slot name="slotName"  <!--作用域插槽也可以具名!-->
          v-for="item in items" 
          :dos="item.do"  <!--取循环中的项作为属性 方便父组件调用!-->
     ></slot>
    </ul>
 </template>
 <script>
   export default{
     props:['items'] <!--父级items 传过来的值!-->
   }
</script>

  • 父子通信: 父向子传递数据是通过 props,子向父是通过 events(emit),通过父链/子链也可以通信(emit),通过父链 / 子链也可以通信(parent / children),ref也可以访问组件实例,provide/injectAPIchildren),ref 也可以访问组件实例,provide / inject API,attrs/$listeners

  • 兄弟通信: Bus,Vuex

  • 跨级通信: Bus,Vuex,provide / inject API,attrs/attrs/listeners