vue 组件通信

这是我参与更文挑战的第19天,活动详情查看: 更文挑战

组件是 vue 的强大功能之一,而各个组件的作用域又是独立的,也就是各组件的数据是相对独立的,因此组件之间的通信就变成了核心要解决的问题,通常情况组件有一下几种情况:

如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C、A 和 D 是隔代关系(可能隔多代)。

方法一:props / $emit

此方法用于父子组件的通信。父组件 A 通过 props 的方式向子组件 B 传递,在子组件 B 中通过 $emit 的方式向父组件 A 传值, A 组件中使用 v-on 接收 B 组件传过来的值。举个例子:

1 父组件向子组件传值:

父组件代码:

<template>
    <div class="list">
        <item :goods="goods"></item>
    </div>
</template>
<script>
import Item from "./components/Item"
export default {
  name: 'App',
  data(){
    return{
      goods: {
        name: 'iPhone XS Max',
        price: 10000
      }
    }
  },
  components:{
    "item":item
  }
}
</script>
复制代码

子组件代码:

<template>
 <div>
    商品名称:{{goods.name}}
    商品价格:{{goods.price}}
 </div>
</template>
<script>
export default {
  props: {
    goods: {
      type: Object,
      default: null
    }
  }
}
</script>
复制代码

父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:datapropscomputed

2 子组件向父组件传值

子组件

<template>
  <div>
    商品名称:{{goods.name}}
    商品价格:{{goods.price}}
    <el-button @click='changePrice'>点击</el-button>
  </div>
</template>
<script>
export default {
  props: {
    goods: {
      type: Object,
      default: null
    }
  },
  methods: {
    changePrice () {
      // 派发 change 事件供父组件监听,并传递值 “子组件向父组件的值”
      this.$emit('change', '子组件向父组件传的值')
    }
  }
}
</script>
复制代码

父组件

<template>
  <div class="hello">
    <item
      :goods="goods"
      @change='changePrice'
    ></item>
  </div>
</template>

<script>
import Item from '@/components/Item'
export default {
  data () {
    return {
      goods: {
        name: 'iPhone XS Max',
        price: 10000
      }
    }
  },
  methods: {
    // 使用 v-on(缩写成@)监听 change 事件,参数为子组件传过来的值
    changePrice (val) {
      console.log(val)
      this.goods.price = val
    }
  },
  components: { Item }
}
</script>
复制代码

方法二:中央事件总线 eventBus $emit / $on

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。

1 具体实现方式

var Event=new Vue();  // 定义一个新的Vue实例
Event.$emit(事件名,数据);   // 使用 $emit 来传递
Event.$on(事件名,data => {});  // 使用 $on  来接收
复制代码

2 例子说明

A、B、C是三个兄弟组件,A 和 B 要传递数据给 C,C 要从 A 和 B 中接收数据。

A.vue

<template>
  <div>
    <el-button @click='giveDataToC'>A传值给C</el-button>
  </div>
</template>
<script>
import Vue from 'vue'
window.Event = new Vue()
export default {
  methods: {
    giveDataToC () {
      window.Event.$emit('data-A', 'data from A')
    }
  }
}
</script>
复制代码

B.vue

<template>
  <div>
    <el-button @click='giveDataToC'>B传值给C</el-button>
  </div>
</template>
<script>
export default {
  methods: {
    giveDataToC () {
      window.Event.$emit('data-B', 'data from A')
    }
  }
}
</script>
复制代码

C.vue

$on 监听了自定义事件 data-Adata-B,因为有时不确定何时会触发事件,一般会在 mountedcreated 钩子中来监听。

<template>
  <div>
    <div>来自A的数据:{{A}}</div>
    <div>来自B的数据:{{B}}</div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      A: '',
      B: ''
    }
  },
  mounted () {
    window.Event.$on('data-A', val => {
      this.A = val
    })
    window.Event.$on('data-B', val => {
      console.log('form')
      this.B = val
    })
  }
}
</script>
<style scoped>
div {
  margin-bottom: 20px;
}
</style>
复制代码

注:要使用同一个 Event

方法三 $attrs / $listeners

vue 2.4 提供的多组件嵌套的传值,在中间不对数据做处理。

1 解释

$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

2 例子

parent.vue

<template>
  <div>
    <strong>这是父组件</strong>
    <child-a
      :content=content
      :shape=shape
      :act=act
      :lib=lib
      title="前端所需"
      @event1="ev1"
      @event2="ev2"
    ></child-a>
  </div>
</template>
<script>
import ChildA from '@/components/children/childA'
export default {
  components: { ChildA },
  data () {
    return {
      content: 'html',
      shape: 'css',
      act: 'Javascript',
      lib: 'Vue'
    }
  },
  methods: {
    ev1 () {
      console.log('ev1')
    },
    ev2 () {
      console.log('ev2')
    }
  }
}
</script>
<style scoped>
div{
  color: red;
  margin: 30px;
}
</style>
复制代码

childA.vue

<template>
  <div>
    <p>这是组件A</p>
    <p>content: {{ content }}</p>
    <p>childA的$attrs: {{ $attrs }}</p>
    <child-b
      v-bind="$attrs"
      v-on="$listeners"
    ></child-b>
  </div>
</template>
<script>
import ChildB from '@/components/children/ChildB'
export default {
  props: {
    content: String
  },
  components: { ChildB },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  created () {
    console.log(this.$attrs);
  },
  mounted () {
    this.$emit('event1')
    console.log(this.$listeners)
  }
}
</script>
<style scoped>
div{
  padding-top: 30px;
  color: green;
}
p{
  line-height: 2
}
</style>
复制代码

childB.vue

<template>
  <div>
    <p>这是组件B</p>
    <p>shape: {{ shape }}</p>
    <p>childB的$attrs: {{ $attrs }}</p>
    <child-c
      v-bind="$attrs"
      v-on="$listeners"
    ></child-c>
  </div>
</template>
<script>
import ChildC from '@/components/children/ChildC'
export default {
  props: {
    shape: String
  },
  components: { ChildC },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  created () {
    console.log(this.$attrs);
  }
}
</script>
<style scoped>
  div{
    padding-top: 30px; 
  }
  p{
    line-height: 2;
  }
</style>
复制代码

childC.vue

<template>
  <div>
    <p>这是组件C:</p>
    <p>childC的$attrs: {{ $attrs }}</p>
    <p>lib:{{lib}}</p>
    <p>title:{{title}}</p>
    <el-button @click="event1">ev1</el-button>
  </div>
</template>
<script>
export default {
  props: {
    lib: String,
    title: String
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  created () {
    console.log(this.$attrs);
  },
  mounted () {

  },
  methods: {
    event1 () {
      this.$listeners.event1()
    }
  }
}
</script>
<style scoped>
div{
  padding-top: 30px;
  color: yellowgreen;
}
p{
  line-height: 2;
}
</style>
复制代码

综上,在父组件中定义数据以及事件,子组件、孙组件以及更多后代组件中的 $attrs 会继承除组件自身 props 中的属性,子组件、孙组件以及更多后代组件可以使用 this.$listener.事件名 来执行父组件中的事件。

方法四 provide / inject

这两个方法是在 vue 2.2.0 中新增的。

1 解释

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。

inject 选项应该是:

  • 一个字符串数组,或
  • 一个对象,对象的 key 是本地的绑定名,value 是:
    • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
    • 一个对象,该对象的:
      • from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
      • default 属性是降级情况下使用的 value

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

2 例子

父组件.vue

<template>
  <div>
    <child-a></child-a>
    <el-button @click='change'>change</el-button>
  </div>
</template>
<script>
import ChildA from '@/components/provide/ChildA'
export default {
  provide: {
    name: '这是一个 provide 的数据'
  },
  components: { ChildA },
  methods: {
    change () {
      this.name = '修改一下看看子组件变不变'
    }
  }
}
</script>
<style scoped>
div{
  margin: 50px;
}
</style>
复制代码

子组件.vue

<template>
  <div>
    来自父组件的内容: {{name}}
  </div>
</template>
<script>
export default {
  inject: ['name']
}
</script>
复制代码

可以看到,在父组件中定义 provide,在子组件中使用 inject 就可以得到父组件 provide 中的值,但是我们点击修改父组件的值子组件的值并不会发生改变。

3 provide/inject 实现响应式

provide 祖先组件的实例,在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如 propsmethods
使用2.6最新API Vue.observable 优化响应式 provide(推荐)

方法一例子:

父组件

<template>
  <div>
    <p>
      父组件
      <el-button @click="change('red')">改变Color</el-button>
    </p>

    <child-a></child-a>
  </div>
</template>
<script>
import ChildA from "@/components/provide/ChildA";
export default {
  data() {
    return {
      color: "blue"
    };
  },
  provide() {
    return {
      theme: this // 方法一:提供祖先组件的实例
    };
  },
  components: { ChildA },
  methods: {
    change(color) {
      if (color) {
        this.color = color;
      } else {
        this.color = this.color === "blue" ? "red" : "blue";
      }
    }
  }
};
</script>
<style scoped>
div {
  margin: 50px;
}
</style>
复制代码

子组件

<template>
  <div :style="{ color: theme.color }">子组件A来自父组件的内容</div>
</template>
<script>
export default {
  inject: {
    theme: {
      //函数式组件取值不一样
      default: () => ({})
    }
  }
};
</script>
复制代码

方法二例子:

父组件

<template>
  <div>
    <p>
      父组件
      <el-button @click="change('red')">改变Color</el-button>
    </p>

    <child-a></child-a>
  </div>
</template>
<script>
import ChildA from "@/components/provide/ChildA";
import Vue from "vue";
export default {
  data() {
    return {
      color: "blue"
    };
  },
  provide() {
    this.theme = Vue.observable({
      color: "blue"
    });
    return {
      theme: this.theme
    };
  },
  components: { ChildA },
  methods: {
    change(color) {
      if (color) {
        this.theme.color = color;
      } else {
        this.theme.color = this.theme.color === "blue" ? "red" : "blue";
      }
    }
  }
};
</script>
<style scoped>
div {
  margin: 50px;
}
</style>
复制代码

子组件

<template>
  <div :style="{ color: theme.color }">子组件A来自父组件的内容</div>
</template>
<script>
export default {
  inject: {
    theme: {
      //函数式组件取值不一样
      default: () => ({})
    }
  }
};
</script>
复制代码

方法五 $parent / $childrenref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例

父组件.vue

<template>
  <div>
    {{dataA}}
    <child-a ref="childA"></child-a>
  </div>
</template>
<script>
import ChildA from "@/components/ref/ChildA";
export default {
  data() {
    return {
      dataA: "",
      dataParent: "这是来自父组件的数据"
    };
  },
  components: { ChildA },
  mounted() {
    const childA = this.$refs.childA;
    console.log(childA.name1);
    this.dataA = childA.name1;
    console.log(this.$children[0].name2);
    childA.operationA();
  }
};
</script>
复制代码

子组件.vue

<template>
  <div>{{dataFrom}}</div>
</template>
<script>
export default {
  data() {
    return {
      name1: "这是来自子组件A的数据",
      name2: "这也是来自子组件A的数据",
      dataFrom: ""
    };
  },
  mounted() {
    this.dataFrom = this.$parent.dataParent;
  },
  methods: {
    operationA() {
      console.log("这是来自子组件的操作");
    }
  }
};
</script>
复制代码

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。它无法在兄弟或跨级组件之间通信。可以直接使用 $parent 获取父组件,从而访问父组件的数据以及方法;可以通过 $children 获取子组件(获取出来的是一个子组件的数组),从而访问子组件的数据以及方法。

方法六 $boradcast / $dispatch

这也是一对成对出现的方法,不过只是在Vue1.0中提供了,而Vue2.0被废弃了。

方法七 Vuex

image.png

  • Vue ComponentsVue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。
  • dispatch:操作行为触发方法,是唯一能执行action的方法。
  • actions:操作行为处理模块,由组件中的 $store.dispatch('action 名称', data1) 来触发。然后由 commit() 来触发 mutation 的调用 , 间接更新 state。负责处理 Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
  • commit:状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。
  • mutations:状态改变操作方法,由 actions 中的 commit('mutation 名称') 来触发。是 Vuex 修改 state 的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
  • state:页面状态管理容器对象。集中存储 Vue components 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
  • gettersstate 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享