目录

父子互相通信

proprs / emits

使用 props(父传子), 使用emit(子传父)

v-model / emits(父子互相通信))

仅父传子通信

ref方式

仅父传更深的后代(仅爷传孙通信)

provide / inject(组件内api)

provide / inject响应性数据的传递与接收

兄弟组件通信

全局组件通信

EventBus(通常被称之为 “全局事件总线”)

Vuex(后续文章详解)


父子互相通信

proprs / emits

使用 props(父传子), 使用emit(子传父)

父组件:

<template>
   // 通过v-bind将数据想子组件传递
   // 动态绑定 props 是用 :,绑定 emit 是用 @
  <VChild :value="valueData"  @update-age="updateAge" />
</template>
const valueData = 'type'

子组件:

export default defineComponent({
  props: {
    value: String,
  },
  // 在 2.x ,只需要通过 this.uid、this.userName 就可以使用父组件传下来的 prop 。
  // 但是 3.x 没有了 this, 需要给 setup 添加一个入参才可以去操作。
  setup (props, ctx) {
    // 该入参包含了我们定义的所有props
    console.log(props);
    // 调用 emits
    ctx.emit('update-age', value)
  }
})

TIP:

  1. prop 是只读,不允许修改
  2. setup 的第一个入参,包含了我们定义的所有props(如果在 Child.vue 里未定义,但 父组件 Father.vue 那边非要传过来的,不会拿到,且控制台会有警告信息)
  3. 该入参可以随意命名,比如你可以写成一个下划线 _,通过 _.uid 也可以拿到数据,但是语义化命名,是一个良好的编程习惯。

v-model / emits(父子互相通信))

通过Vue3的文档可以发现,这个指令的用法发生了一定的变化。在之前,我们要想实现一个自定义的非表单组件的双向绑定,需要通过xxxx.sync的这种语法来实现,如今这个指令已经被废除了,而是统一使用v-model这个指令。

父组件:支持多个数据的双向绑定

<template>
  <VChild v-model:value="valueData" v-model:keyword="keywordData" />
</template>

子组件:

<template>
  <button @click="clickHandle">click</button>
</template>
export default defineComponent({
  name: 'child',
  props: {
    value: String,
    keyword: String
  },
  setup(props, ctx) {
     // 用户点击按钮
    const clickHandle = (e: any) => {
      // 修改对应的props的数据,直接通过 “update:属性名” 的格式,直接定义一个更新事件
      ctx.emit('update:value', value)
      ctx.emit('update:keyword', value + '123')
    }
  }
})

TIP:

虽然 v-model 的配置和 prop 相似,但是为什么出这么两个相似的东西?自然是为了简化一些开发上的操作。

使用 props / emits,如果要更新父组件的数据,还需要在父组件定义好方法,然后 return 给 template 去绑定事件给子组件,才能够更新。

而使用 v-model / emits ,无需如此,可以在 Child.vue 直接通过 “update:属性名” 的格式,直接定义一个更新事件。

仅父传子通信

父组件向子组件传递一个数据,可以用这两种方式:

ref方式

父组件:

<template>
  <div>sonRef</div>
  <button @click="sendValue">send</button>
  // 这里ref接受的字符串,要setup返回的ref类型的变量同名
  <Son ref="sonRef" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Son from '@/components/Son.vue'
export default defineComponent({
  name: 'Demo',
  components: {
    Son
  },
  setup() {
    // 如果ref初始值是一个空,可以用于接受一个实例
    // vue3中获取实例的方式和vue2略有不同
    const sonRef: any = ref(null)
    // 请保证视图渲染完毕后再执行操作
    onMounted( () => {
      // 可以拿到son组件实例,并调用其setup返回的所有信息
      console.log(sonRef.value)
      // 通过调用子组件实例的方法,向其传递数据
      sonRef.value.acceptValue('123456')
      // 也可以去操作子组件的数据
      sonRef.value.valueRef = '8888';
    });
    // 必须return出去才可以给到template使用
    return {
      sonRef, 
    }
  }
})
</script>

子组件:

<template>
  // 渲染从父级接受到的值
  <div>Son: {{ valueRef }}</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'Son',
  setup() {
    const valueRef = ref('')
    // 该函数可以接受父级传递一个参数,并修改valueRef的值
    const acceptValue = (value: string) => (valueRef.value = value)
    return {
      acceptValue,
      valueRef
    }
  }
})
</script>

TIP:

这种方式跟Vue2中使用this.$refs,this.$children的方式很相似,都是通过拿到子组件实例,直接调用子组件身上的函数。方法千篇一律,不过在Vue3中没有了this这个黑盒。

仅父传更深的后代(仅爷传孙通信)

provide / inject(组件内api)

这个特性有两个部分:Grandfather.vue 有一个 provide 选项来提供数据,Grandson.vue 有一个 inject 选项来开始使用这些数据。

  1. Grandfather.vue 通过 provide 向 Grandson.vue 传值(可包含定义好的函数)
  2. Grandson.vue 通过 inject 向 Grandfather.vue 触发爷爷组件的事件执行

无论组件层次结构有多深,发起 provide 的组件都可以作为其所有下级组件的依赖提供者。

爷组件:

<template>
  <Grandfather>
    <father></father>
  </Grandfather>
</template>
<script lang="ts">
// 记得导入provide
import { defineComponent, provide } from 'vue'
export default defineComponent({
  name: 'Grandfather',
  setup(props) {
      // 定义好数据
    const msgData: string = 'Hello World!';
    // 向后代provide出去数据
    provide(msg, msgData)
    return {}
  }
})
</script>

TIP:

在 3.x,provide需要导入并在setup里启用,并且现在是一个全新的方法。

每次要provide一个数据的时候,就要单独调用一次。

孙组件:

<script lang="ts">
// 记得导入inject
import { defineComponent, reactive, inject } from 'vue'
export default defineComponent({
  name: 'Grandson',
  setup () {
    const msg: string = inject('msg') || '';
  }
})
</script>

provide / inject响应性数据的传递与接收

provide 和 inject 本身不可响应,但是并非完全不能够拿到响应的结果,只需要我们传入的数据具备响应性,它依然能够提供响应支持。

我们以 ref 和 reactive 为例,来看看应该怎么发起 provide 和接收 inject。

对这 2 个 API 还不熟悉的同学,建议先阅读一下 响应式性基础 。

爷组件:

export default defineComponent({
  // ...
  setup () {
    // provide一个ref
    const msg = ref<string>('Hello World!');
    provide('msg', msg);
    // provide一个reactive
    const userInfo: Member = reactive({
      id: 1,
      name: 'Petter'
    });
    provide('userInfo', userInfo);
    // 2s 后更新数据
    setTimeout(() => {
      // 修改消息内容
      msg.value = 'Hi World!';
      // 修改用户名
      userInfo.name = 'Tom';
    }, 2000);
  }
})

孙组件:

export default defineComponent({
  setup () {
    // 获取数据
    const msg = inject('msg');
    const userInfo = inject('userInfo');
    // 打印刚刚拿到的数据
    console.log(msg);
    console.log(userInfo);
    // 因为 2s 后数据会变,我们 3s 后再看下,可以争取拿到新的数据
    setTimeout(() => {
      console.log(msg);
      console.log(userInfo);
    }, 3000);
    // 响应式数据还可以直接给 template 使用,会实时更新
    return {
      msg,
      userInfo
    }
  }
})

provide / inject引用类型的传递与接收

TIP:

组件内的provide / inject 区分于应用配置内的应用API——provide。(以后文章详细讲解)

兄弟组件通信

兄弟组件是指两个组件都挂载在同一个 Father.vue 下,但两个组件之间并没有什么直接的关联。如果想要交流:

  1. 先把数据传给共同的Father.vue,再通过父子组件通信去交流;(难用,不推荐)
  2. 使用下面的全局组件通信。(√)

全局组件通信

全局组件通信是指,两个任意的组件,不管是否有关联(父子、爷孙、兄弟)的组件,都可以直接进行交流的通信方案。

EventBus(通常被称之为 “全局事件总线”)

Vue 3.x 移除了 $on 、 $off 和 $once 这几个事件 API,使得vue3.x不能像2.x一样,不能直接使用EventBus。

Vuex(后续文章详解)

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。