用vue可以是要组件复用的,而组件实例的作用域是相互独立,这意味着不同组件之间的数据无法互相引用
一般来说,组件之间可以有几种关系:
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。
props
的方式向子组件传递//App.vue父组件
<template>
<div id="app">
<users v-bind:users="users">users> //前者自定义名称便于子组件调用,后者要传递数据名
div>
template>
<script>
import Users from "./components/Users"
export default {
name: 'App',
data(){
return{
users:["Henry","Bucky","Emily"]
}
},
components:{
"users":Users
}
}
script>
//users子组件
<template>
<div class="hello">
<ul>
<li v-for="user in users">{{user}}li>//遍历传递过来的值,然后呈现到页面
ul>
div>
template>
<script>
export default {
name: 'HelloWorld',
props:{
users:{ //这个就是父组件中子标签自定义名字
type:Array,
required:true
}
}
}
script>
$emit
向父组件传值// 子组件
<template>
<header>
<h1 @click="changeTitle">{{title}}h1>//绑定一个点击事件
header>
template>
<script>
export default {
name: 'app-header',
data() {
return {
title:"Vue.js Demo"
}
},
methods:{
changeTitle() {
this.$emit("titleChanged","子向父组件传值");//自定义事件 传递值“子向父组件传值”
}
}
}
script>
// 父组件
<template>
<div id="app">
<app-header v-on:titleChanged="updateTitle" >app-header>//与子组件titleChanged自定义事件保持一致
// updateTitle($event)接受传递过来的文字
<h2>{{title}}h2>
div>
template>
<script>
import Header from "./components/Header"
export default {
name: 'App',
data(){
return{
title:"传递的是一个值"
}
},
methods:{
updateTitle(e){ //声明这个函数
this.title = e;
}
},
components:{
"app-header":Header,
}
}
script>
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地 实现了任何组件间的通信,包括父子、兄弟、跨级 。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
<div id="itany">
<my-a>my-a>
<my-b>my-b>
<my-c>my-c>
div>
<template id="a">
<div>
<h3>A组件:{{name}}h3>
<button @click="send">将数据发送给C组件button>
div>
template>
<template id="b">
<div>
<h3>B组件:{{age}}h3>
<button @click="send">将数组发送给C组件button>
div>
template>
<template id="c">
<div>
<h3>C组件:{{name}},{{age}}h3>
div>
template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
template: '#a',
data() {
return {
name: 'tom'
}
},
methods: {
send() {
Event.$emit('data-a', this.name);
}//发送name,给空的父组件,事件名叫data-a
}
}
var B = {
template: '#b',
data() {
return {
age: 20
}
},
methods: {
send() {
Event.$emit('data-b', this.age);
}//发送age,给空的父组件,事件名叫data-b
}
}
var C = {
template: '#c',
data() {
return {
name: '',
age: ""
}
},
mounted() {//在模板编译完成后执行
Event.$on('data-a',name => {
this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
})//通过父组件传name值过来,通过$on来监听data-a事件
Event.$on('data-b',age => {
this.age = age;
})//通过父组件传age值过来,通过$on来监听data-a事件
}
}
var vm = new Vue({
el: '#itany',
components: {
'my-a': A,
'my-b': B,
'my-c': C
}//新建一个空的父组件,用来传递A、B的值给C(将ABC都设为这个父组件的子组件)
});
script>
$on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。
跨级组件之间传递
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//数据,相当于data
state: {
},
getters: {
},
//里面定义方法,操作state方发
mutations: {
},
// 操作异步操作mutation
actions: {
},
modules: {
},
})
vuex中一共有五个状态 State Getter Mutation Action Module
提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data
定义
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//数据,相当于data
state: {
name:"张三",
age:12,
count:0
},
})
调用:
<p>{{$store.state.name}}p>
<p>{{$store.state.age}}p>
this.$store.state.name
// 从vuex中按需导入mapstate函数
import { mapState } from "vuex";
// 注意:当前组件需要的全局数据,映射为当前组件computed属性
computed: mapState([
// 映射 this.count 为 store.state.count
'name','age','sex'
])
//使用
<p>{{name + age}}</p>
定义
有时候我们需要从 store 中的 state 中派生出一些状态
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//数据,相当于data
state: {
name:"张三",
age:12,
count:0
},
getters: {
countTodos(){
retuen count+1
},
// 通过属性访问
// Getter 接受 state 作为其第一个参数,接受其他 getter 作为第二个参数
ageTodos (state,getters) {
return state.age + getters.countTodos
}
//通过方法访问
getTodoById: (state) => (id) => {
return state.count.find(todo => count === id)
}
}
})
调用:
<p>{{$store.getters.countTodos}}p>
this.$store.getters.countTodos
// 从vuex中按需导入mapGetters函数
import { mapGetters } from "vuex";
// 注意:当前组件需要的全局数据,映射为当前组件computed属性
computed: mapGetters([
// 映射 this.count 为 store.state.count
'countTodos'
])
//使用
<p>{{countTodos}}</p>
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//数据,相当于data
state: {
name:"张三",
age:12,
count:0
},
mutations: {
// 接受 state 作为第一个参数
addcount(state,num){
state.count=+state.count+num
},
reduce(state){
state.count--
}
}
})
使用
<button @click='btn'>增加数据button>
<button @click='btn1'>减少数据button>
// 方法1
methods:{
//加法
btn(){
this.$store.commit("addcount",10) //每次加十
}
//减法
btn1(){
this.$store.commit("reduce")
}
}
// 方法2
methods:{
...mapMutations(["addcount","reduce"])
//加法
btn(){
this.addcount(10) //每次加十
}
//减法
btn1(){
this.reduce()
}
}
Action和Mutation相似,Mutation 不能进行异步操作,若要进行异步操作,就得使用Action
定义
actions: {
//接受一个与 store 实例具有相同方法和属性的 context 对象
asyncAdd(context) {
setTimeout(() => {
context.commit('reduce')
},1000)
}
}
使用
this.$store.dispatch("reduce")
使用辅助函数
...mapActions(["asyncAdd"]),
btn2(){
this.asyncAdd()
}
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
this.store.state.a // -> 获得moduleA 的状态
this.store.state.b // -> 获得moduleB 的状态
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
$attrs
–继承所有的父组件属性(除了 prop 传递的属性、class 和 style ),一般用在子组件的子元素上,并且可以通过 v-bind=" $attrs"
传入内部组件。
$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=" $listeners"
传入内部组件
同族跨级组件数据传输
A组件(App.vue)
<template>
<div id="app">
<child1 :pchild1="child1" :pchild2="child2" :pchild3="child3" @method1="onMethod1" @method2="onMethod2">child1>
div>
template>
<script>
import Child1 from "./Child1.vue";
export default {
data() {
return {
child1:'1',
child2: 2,
child3:{
name:'child3'
}
};
},
components: { Child1 },
methods: {
onMethod1(msg1) {
console.log(`${msg1} running`);
},
onMethod2(msg2) {
console.log(`${msg2} running`);
},
},
};
script>
B组件(Child1.vue)
<template>
<div class="child-1">
<h2>in child1h2>
<p>props: {{ pchild1 }}p>
<p>$attrs: {{ $attrs }}p>
<hr/>
<child2 v-bind="$attrs" v-on="$listeners">child2>
div>
template>
<script>
import Child2 from "./Child2.vue";
export default {
data() {
return {
child1:'child1'
};
},
components: { Child2 },
props: {
pchild1:{
type:String
}
},
inheritAttrs: false,
mounted() {
this.$emit("method1",this.child1);
},
};
script>
C 组件 (Child2.vue)
<template>
<div class="child-2">
<h2>in child2:h2>
<p>props: {{ pChild2 }}p>
<p>$attrs: {{ $attrs }}p>
<p>pchild3Name: {{ $attrs.pchild3.name }}p>
<hr/>
div>
template>
<script>
export default {
data() {
return {
child2:'child2'
};
},
props: {
pChild2:{
type:String,
}
},
inheritAttrs: false,
mounted() {
this.$emit("method2",this.child2);
},
};
script>
provide
和 inject
选项需要一起使用,它允许祖先组件向其所有子孙组件注入依赖,并在其上下游关系成立的时间里始终生效,不论组件层级有多深。
// 祖先.vue
export default {
provide: {
name: 'albertos'
}
}
/**************************************/
// 子孙.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // albertos
}
}
ref
:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例// component-a 子组件
export 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;//拿到comA这个引用对象
console.log(comA.title);
comA.sayHello(); // 调用comA中的方法
}
}
script>
EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的“灾难”,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。
首先需要创建事件总线并将其导出,以便其它模块可以使用或者监听它。我们可以通过两种方式来处理。先来看第一种,新创建一个 .js 文件,比如 event-bus.js
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
<template>
<button @click="sendMsg()">-button>
template>
<script>
import { EventBus } from "../event-bus.js";
export default {
methods: {
sendMsg() {
EventBus.$emit("aMsg", '来自A页面的消息');
}
}
};
script>
<template>
<p>{{msg}}p>
template>
<script>
import {
EventBus
} from "../event-bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
EventBus.$on("aMsg", (msg) => {
// A发送来的消息
this.msg = msg;
});
}
};
script>
前面提到过,如果使用不善,EventBus会是一种灾难,到底是什么样的“灾难”了?大家都知道vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。还要就是如果业务有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理EventBus在项目中的关系。通常会用到,在vue页面销毁时,同时移除EventBus事件监听。
import {
eventBus
} from './event-bus.js'
EventBus.$off('aMsg', {})
你也可以使用 EventBus.$off('aMsg')
来移除应用内所有对此某个事件的监听。或者直接调用 EventBus.$off()
来移除所有事件频道,不需要添加任何参数
props
,子向父是通过 events($ emit
)$ parent / $ children
);ref
也可以访问组件实例;provide / inject
API;$attrs/ $listeners