Skip to content

7. vue组件间通信方式

父子组件间传值

props(vue3:defineProps)/$emit

父组件通过props的方式向子组件传递数据,子组件通过$emit向父组件通信

vue2

父组件

html
 <!--父组件-->
 <template>
  <div class="section">
    <com-article :articles="articleList" @onEmit='onemit'></com-article>
  </div>
</template>

<script>
import Child from './test/article.vue'
export default {
  name: 'HelloWorld',
  components: { Child },
  data() {
    return {
      articleList: ['红楼梦', '西游记', '三国演义']
    }
  },
  methods: {
    onemit(e) {
      // 接受子组件传来的值
      console.log(e) // 1
    }
  }
}
</script>

子组件

html
<template>
  <div>
    <span v-for="(item, index) in articles" :key="index">{{item}}</span>
    <div @click='toemit'>向父组件传值</div>
  </div>
</template>

<script>
export default {
  // 接受父组件传来的props
  props: ['articles'],
  // 可以对props做基础的数据验证
  // props: {
  //   propsA: {
  //     type: String/Number..,
  //     required: true
  //     default: 100
  //     default: function() {
  //       return {message: 100}
  //    } 
  //  }
  // }
  methods: {
    toEmit() {
      // 向父组件传一个1
      this.$emit('onemit', 1)
    }
  }
}
</script>

vue3 - Composition API

父组件

html
<child :msg2="msg2" @parentClick="parentClick" @parentChang="parentChang"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const msg2 = ref("这是传给子组件的信息2")
    // 或者复杂类型
    const msg2 = reactive(["这是传给子组件的信息2"])
    const parentClick = (data: number) => { alert('子组件触发了此事件,传递的值是' + data) }
    const parentChang = () => { alert('子组件触发了此事件') }
</script>

子组件

html
// Child.vue 接收
<template>
    <button @click="handClick">点击触发handClick事件</button>
    <input type="checkbox" @chang="handChang">
</template>
<script setup>
    // 不需要引入 直接使用
    // import { defineProps } from "vue"
    // 接受props
    const props = defineProps({
        // 写法一
        msg2: String
        // 写法二
        msg2:{
            type:String,
            default:""
            ...
        }
    })
    console.log(props) // { msg2:"这是传级子组件的信息2" }

    const emits = defineEmits(['parentClick','parentChang']) // 语法糖
    // 定义类型
    // const emits = defineEmits<{
    //   (e: 'parentClick', data: number): void // 函数类型 当传递的值的类型不是number时会报错
    //   (e: 'parentChang'): void // 函数类型
    // }>();
    const handClick = () => {
        emits('parentClick',2) // 使用方式和 vue2 this.$emit 一样
    }
    const handChang = () => {
        emits('parentChang') // 使用方式和 vue2 this.$emit 一样
    }
</script>

总结: prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。

$children和$parent(仅vue2)

通过$parent$children就可以访问组件的实例,就可以访问此组件的所有方法和数据

html
// 父组件中
<template>
  <div class="hello_world">
    <div>{{msg}}</div>
    <com-a></com-a>
    <button @click="changeA">点击改变子组件值</button>
  </div>
</template>

<script>
import ComA from './test/comA.vue'
export default {
  name: 'HelloWorld',
  components: { ComA },
  data() {
    return {
      msg: 'Welcome'
    }
  },

  methods: {
    changeA() {
      // 获取到子组件A
      this.$children[0].messageA = 'this is new value'
    }
  }
}
</script>
html
// 子组件中
<template>
  <div class="com_a">
    <span>{{messageA}}</span>
    <p>获取父组件的值为:  {{parentVal}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      messageA: 'this is old'
    }
  },
  computed:{
    parentVal(){
      return this.$parent.msg;
    }
  }
}
</script>

要注意边界情况,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent和$children的值不一样,$children 的值是数组,而$parent是个对象

ref/refs(vue2)、expose(vue3)

  • 在普通DOM元素上使用就指向DOM元素
  • 用在子组件上就指向组件实例

vue2(ref,$refs)

html
// 父组件 app.vue

<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.name);  // Vue.js
      comA.sayHello();  // hello
    }
  }
</script>
html
// 子组件 A.vue
<script>
export default {
  data () {
    return {
      name: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      console.log('hello')
    }
  }
}
</script>

vue3(ref, expose)

html
// 父组件
<template>
  <Child ref='childRef'/>
  <button @click='getChildData'></button>
</template>

<script setup>
  import {ref} from 'vue'
  const childRef = ref(null)
  const getChildData = () => {
    console.log(childRef.value.name) // child
  }
</script>
html
// 子组件
<script setup>
  import {ref} from 'vue'
  const name = ref('child')
  defineExpose({
    name
  })
</script>

兄弟、爷孙(隔代)组件

provide / inject

爷(父)组件通过provide提供变量,孙(子)组件中通过inject注入变量

注意: 这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据,而不局限于只能从当前父组件的props属性中回去数据

vue2

假设有三个组件: A.vue、B.vue、C.vue 其中 C是B的子组件,B是A的子组件

html
// A.vue
<template>
  <div>
	<comB></comB>
  </div>
</template>

<script>
  import comB from '../components/test/comB.vue'
  export default {
    name: "A",
    provide: {
      for: "demo"
    },
    components:{
      comB
    }
  }
</script>
html
// B.vue

<template>
  <div>
    {{demo}}
    <comC></comC>
  </div>
</template>

<script>
  import comC from '../components/test/comC.vue'
  export default {
    name: "B",
    inject: ['for'],
    data() {
      return {
        demo: this.for
      }
    },
    components: {
      comC
    }
  }
</script>
html
// C.vue
<template>
  <div>
    {{demo}}
  </div>
</template>

<script>
  export default {
    name: "C",
    inject: ['for'],
    data() {
      return {
        demo: this.for
      }
    }
  }
</script>

vue3

html
  // A.vue
  <script setup>
    import {provide} from 'vue'
    provide('name', 'KESHAOYE')
  </script>
html
  // A的子组件 B.vue
  <script setup>
    import {inject} from 'vue'
    const name = inject('name')
    console.log(name) // KESHAOYE
  </script>
html
  // A的孙组件、B的子组件 C.vue
  <script setup>
    import {inject} from 'vue'
    const name = inject('name')
    console.log(name) // KESHAOYE
  </script>

attrs(vue2、vue3)、listeners(vue2)

attrs可以获取到所有prop获取不到的attribute(除class和style)

vue2

html
// 父组件
  <template>
    <Child :msg1='msg1' :msg2='msg2' @test='test'/>
  </template>
  <script>
    import Child from './Child.vue'
    export default {
      data() {
        return {
          msg1: 'msg1',
          msg2: 'msg2'
        }
      },
      components: {
        Child
      },
      methods: {
        test() {
          console.log('父组件test方法')
        }
      }
    }
  </script>
html
// 子组件
  <template>
    <button @click='getParentProp'>获取父组件传入的属性/方法</button>
  </template>
  <script>
    export default {
      data() {
        return {
          msg1: 'msg1',
          msg2: 'msg2'
        }
      },
      props: {
        msg1: {
          type: 'string',
          default: 'msg1'
        }
      },
      methods: {
        getParentProp() {
          console.log(this.$attrs,this.$listeners) // [msg2: 'msg2'], [test: test()]
        }
      }
    }
  </script>

vue3(attrs)

html
// 父组件
  <template>
    <Child :msg1='msg1' :msg2='msg2'/>
  </template>
  <script setup>
    import Child from './Child'
    import { ref } from 'vue'
    const msg1 = ref('msg1')
    const msg2 = ref('msg2')
  </script>
html
// 子组件
  <script setup>
    import { useAttrs } from 'vue'
    const attrs = useAttrs() 
  </script>

EventBus(vue2)

js
  // 方式一: 创建eventbus.js
  import Vue from 'vue'
  export const EventBus = new Vue()
  // 方式二: 在Vue实例上挂载
  // main.js
  Vue.prototype.$EventBus = new Vue()
js
  // 在需要向外部发送自定义事件的组件内
<template>
    <button @click="handlerClick">按钮</button>
</template>
import Bus from "./Bus.js"
export default{
    methods:{
        handlerClick(){
            // 自定义事件名 sendMsg
            Bus.$emit("sendMsg", "这是要向外部发送的数据")
        }
    }
}

// 在需要接收外部事件的组件内
import Bus from "./Bus.js"
export default{
    mounted(){
        // 监听事件的触发
        Bus.$on("sendMsg", data => {
            console.log("这是接收到的数据:", data)
        })
    },
    beforeDestroy(){
        // 取消监听
        Bus.$off("sendMsg")
    }
}

vue3(mitt)

由于vue3没有了EventBus跨组件通信,官方推荐使用mitt代替

  • 首先安装 npm i mitt -s

  • 封装BUS

    js
     // bus.js
     import mitt from 'mitt'
     export default mitt()
  • 在组件中引入使用

    html
     // A组件
     <script setup>
       import mitt from './mitt'
       const handleClick = () => {
        mitt.emit('handleChange','我发出了通知')
       }
     </script>
    html
     // B组件
     <script setup>
       import mitt from './mitt'
       mitt.on('handleChange', (e)=>{console.log(e)})// 我发出了通知
     </script>

    vuex/pinia

    localStorage/sessionStorage

KESHAOYE-知识星球