vue3组件间通信

文章目录

  • 1. 父组件 ==>> 儿子组件
    • 1.1props
      • props基本使用
      • props校验
      • props的typescript用法
    • 1.2 $attr, $parent
  • 2. 父组件 ==>> 后代组件(儿子、孙子、曾孙子...),inject和provide
    • javascript用法
    • typescript用法
  • 3. 儿子组件 ==>> 父组件 $emit
    • 普通用法
    • 事件校验+typescript用法
  • 4. 任意组件 ==>> 任意组件
    • 4.1 全局事件总线
    • 4.2 Vuex

不是保姆级教程,仅提供一些简单的基于脚手架的应用实例和注释,阅读需要一定的vue基础

1. 父组件 ==>> 儿子组件

1.1props

props基本使用

@/components/Son.vue

<template>
    <h1>父组件传来一个数字{{aNumber}}h1>
    <h1>父组件传来一个字符串{{aString}}h1>
    <h1>父组件传来一个布尔值{{aBoolean}}h1>
    <h1>>父组件传来一个对象{{anObject}}h1>
    <h1>父组件传来一个对象的所有属性 name:{{name}} age:{{age}}h1>
template>

<script>
    export default {
        name:'Son',
        props:['aNumber','aString','aBoolean', 'anObject','name','age',],
        setup(props) {
            console.log(props)  
            //输出为:Proxy {aNumber: 1219, aString: 'hello', aBoolean: 'false', anObject: Proxy, name: 'jack', …}
            return {}
        }
    }
script>
<style>style>

@/components/Father.vue

<template>
    <Son
        :aNumber="1219"
        :aString="'hello'"
        :aBoolean="false"
        :anObject="person"
        v-bind="person"
    />
template>

<script>
    import {reactive} from 'vue'
    import Son from './Son.vue'
    export default {
        name:'Father',
        components:{
            Son,
        },
        setup(){
            let person = reactive({
                name:'jack',
                age:999
            })
            return {
                person
            }
        }
    }
script>
<style>style>

props校验

父元素中传入参数方法不变,在子元素中以如下方式声明props校验

export default {
    props:{
        name: {
            type:String,
            //required为true说明该参数必传,否则控制台会报警告
            required:true
        },
        age: {
        	//声明参数age的数据类型
            type:Number,
            //required为false说明该参数可传可不传
            required: false,
            //未传入该参数时的默认值
            default: 80,
        },
    },
    setup () {
        return {}
    }
}

props的typescript用法

父元素中传入参数方法不变,子元素中声明props校验的方式略有变化

export default defineComponent({
    props:{
        name: {
        	//对于基本数据类型,如string,number,则直接写类名就可以了
            type:String,
            required:true
        },
        showInfo: {
        	//对于自定义数据类型,要写成Object as PropType<你所定义的interface名称>
            type: Object as PropType<(name:string, age:number) => void>,
            required: true
        },
    },
    setup (props) {
        return {}
    }
})

1.2 $attr, $parent

@/components/Son.vue

<template>
template>

<script>
    import {getCurrentInstance} from 'vue'
    export default {
        name:'Son',
        props:['aNumber'],
        setup(props, context){
            console.log(context.attrs) 
            //通过$attrs属性获得父组件已写入标签但子组件未用pros接收的属性
            //输出结果为 Proxy {person: Proxy, id: '999', __vInternal: 1}

            let myThis = getCurrentInstance()
            console.log(myThis.parent)
            //getCurrentInstance获得当前实例对象
            //myThis.parent是父组件的实例对象,实例对象拿到了,所有的数据就都能拿到
        }
    }
script>
<style>style>

@/components/Father.vue

<template>
    <Son
        :aNumber="num"
        :person="person"
        id="999"
    />
template>
<script>
    import {reactive, ref} from 'vue'
    import Son from './Son.vue'
    export default {
        name:'Father',
        components:{
            Son,
        },
        setup(){
            let num = ref(1219)
            let person = reactive({
                name:'green',
                age:888
            })
            return {
                num,
                person
            }
        }
    }
script>
<style>style>

2. 父组件 ==>> 后代组件(儿子、孙子、曾孙子…),inject和provide

javascript用法

如下,父组件Father向孙组件GrandSon传值

@/components/Father.vue 父组件

<template>
    <h1>父组件:姓名--{{person.name}},年龄--{{person.age}}h1>
    <button @click="ageGrow">fu年龄+1button>
    <Son/>
template>

<script>
    import Son from './Son.vue'
    import {provide, reactive} from 'vue';
    export default {
        name:'Father',
        components:{
            Son,
        },
        setup() {
            let person = reactive({
                name:'jack',
                age:250
            })//要传输的数据
            function ageGrow(){
                person.age++
            }//要传输的函数
            provide('person', person)   //传输数据
            provide('ageGrow', ageGrow) //传输函数
            return {
                person,
                ageGrow
            }
        }
    }
script>
<style>style>

/components/Son.vue 儿子组件

<template>
    <GrandSon/>
template>

<script>
    import GrandSon from './GrandSon.vue'
    export default {
        name:'Son',
        components:{GrandSon}
    }
script>

@/components/GrandSon.vue 孙子组件

<template>
    <h1>孙子组件:姓名--{{person.name}},年龄--{{person.age}}h1>
    <button @click="ageGrow">sun年龄+1button>
template>

<script>
    import { inject } from 'vue'
    export default {
        name:'Grandson',
        setup(){
            let person = inject('person')   //接收数据
            let ageGrow = inject('ageGrow') //接收函数
            return {
                person,
                ageGrow
            }
        }
    }
script>
<style>style>

typescript用法

第一种方法:可以使用as关键字进行类型断言,最方便,但不是很严谨。

//person类型声明
export interface Person{
    name:string,
    age:number
}

//父组件中provide
const person:Person = {
	name: '张三',
	age: 19
}
provide('person', person)

//子组件中inject
const person:Person = inject('person') as Person

第二种方法:在inject函数里声明类型

//子组件中inject
const person:Person = inject<Person>('person', {name:'', age:-1})
//inject函数第二个参数为默认值,不加第二个参数则函数返回值的类型为Person|undefined

第三种方法:使用InjectionKey

//在外部文件中声明并导出key
import { InjectionKey } from "vue"
export interface Person{
    name:string,
    age:number
}
export const PersonKey = Symbol() as InjectionKey<Person>

//父文件中provide
const person:Person = {
     name: '张三',
     age: 19
 }
 provide(PersonKey, person)

//子文件中inject
const person:Person = inject(PersonKey, {name:'', age:-1})
//同样,这里inject函数第二个参数为默认值,不加第二个参数则函数返回值的类型为Person|undefined

3. 儿子组件 ==>> 父组件 $emit

普通用法

@/components/Father.vue

<template>
    <h1>子组件传来的数据:姓名--{{person.name}} 年龄--{{person.age}}h1>
    <Son @myEmit="getPerson"/>
    
template>

<script>
    import { reactive } from 'vue'
    import Son from './Son.vue'
    export default {
        name:'Father',
        components:{Son},
        setup(){
            let person = reactive({})
            function getPerson(receivedPerson){
                Object.assign(person, receivedPerson)//Object.assign()非常实用的一个函数, 建议去mdn学一下
            }
            return {
                person,
                getPerson
            }
        }
    }
script>
<style>style>

@/components/Son.vue

<template>
    <button @click="sendPerson">子组件向父组件传值button>
    <button @click="person.age++">年龄+1button>
    
    <h1>子组件数据:姓名--{{person.name}} 年龄--{{person.age}}h1>
template>

<script>
    import { reactive } from 'vue'
    export default {
        name:'Son',
        setup(props, context){  //由于setup中没有this,所以需要通过上下文context来获得emit
            let person = reactive({
                name:'jack',
                age:987
            })
            function sendPerson(){
                context.emit('myEmit', person) //使用emit触发自定义事件
            }
            return {
                person,
                sendPerson
            }
        }
    }
script>
<style>style>

其实所有父组件向子组件传值的方式都可以实现子组件向父组件传值,子组件向父组件传值的本质就是调用父组件提供的方法,然后把值通过实参的形式传给父组件,那么我们只要把父组件的方法传给子组件就可以实现子组件向父组件传值。

事件校验+typescript用法

父组件App.vue

<template>
  <li>{{name}}, {{age}}li>
  <HelloWorld @submit="update" />
  
template>
<script lang="ts">

  import { defineComponent, ref } from '@vue/runtime-core'
  import HelloWorld from './components/HelloWorld.vue'
  export default defineComponent({
    components:{HelloWorld},
    setup(){
      const name = ref('')
      const age = ref(0)
      function update(nameSubmit:string, ageSubmit:number){
        [name.value, age.value] = [nameSubmit, ageSubmit]
      }
      return {name, age, update}
    }
  })

script>

子组件HelloWorld.vue

<template>
  <button @click="triggerSubmit">submitbutton>
template>

<script lang="ts">
  import { defineComponent, ref } from 'vue'
  export default defineComponent({
    emits: {
      //对submit事件进行校验
      submit: (name:string, age:number) =>{
        if(name.length < 10 && age >0){
          return true
        }
        else{
          //校验失败,vue会报警告,但不会报错,依然会触发父组件里的submit事件
          return false
        }
      }
    },
    setup (props, context) {
      const emit = context.emit
      function triggerSubmit(){
        emit('submit', '张三', 20)
        //使用emit触发事件submit
      } 
      return {triggerSubmit}
    }
  })
  /* 
    父组件里的事件对应的函数、子组件的事件校验函数、使用emit触发事件,三者的参数数据类型必须保持一致
  */
script>

4. 任意组件 ==>> 任意组件

4.1 全局事件总线

new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线
	},
})

vue2采用上面的这种方式,通过在Vue原型对象中添加$bus属性来实现事件总线。
由于 VueComponent.prototype.proto === Vue.prototype ,所以在每个组件实例中都可以通过this.$bus的方式来访问到Vue.prototype.$bus对象,而这个对象本质就是Vue对象,自然有$on和$emit两个方法,某个组件通过$on方法来给这个$bus对象绑定事件,然后另外一个组件又通过$emit方法触发$bus对象上的事件,把数据通过参数传递进去,这样就实现了组件间的通信。
但是,vue3中这种方式已经不好用了,不能再往Vue原型对象上塞属性了,而是需要借助外部的库 tiny-emitter。除了把$bus换成emitter之外,实现事件总线的逻辑和方式上几乎和vue2如出一辙。
代码如下:
@/emittter/index.js

import Emitter from 'tiny-emitter'
const emitter = new Emitter()
export default emitter

@/components/ComponentA.vue

<template>
    <h1>来自不知道哪个组件的信息:{{person}}h1>
template>

<script>
    import emitter from '@/emitter';
    import { reactive } from 'vue'
    export default {
        setup(){
            let person = reactive({})
            emitter.on('updatePerson', receviedPerson => { //为emitter实例绑定事件updatePerson
                Object.assign(person, receviedPerson)
            })
            return {person}
        }
    }
script>
<style>style>

@/components/ComponentB.vue

<template>
    <button @click="sendPeson">向不知道哪个组件发送信息button>
template>

<script>
    import emitter from '@/emitter';
    export default {
        setup(){
            let person = {
                name:'jack',
                age: 996
            }
            function sendPeson(){
                console.log(2);
                emitter.emit('updatePerson', person)//触发updatePerson事件,参数为person对象
            }
            return {sendPeson}
        }
    }
script>
<style>style>

tiny-emitter官方文档
Vue官方不建议使用事件总线进行组件间通信:

“在绝大多数情况下,不鼓励使用全局的事件总线在组件之间进行通信。虽然在短期内往往是最简单的解决方案,但从长期来看,它维护起来总是令人头疼”

4.2 Vuex

Vuex内容请点击这

暂时就学了这么多,待继续更新

你可能感兴趣的:(学习,vue3,vue.js,前端框架,前端,javascript)