vue3组件通信方式

时间:2022-10-13 07:59:44

vue3组件传值共8种方式

1. props传值

props适用于父传子,有两种写法,混合写法和纯vue3写法(语法糖)

1. 混合写法(不推荐!!!)

// 父组件
<script setup lang="ts">
import child from '../components/Child.vue'
</script>

<template>
  <child msg="父组件的消息"/>
</template>


// 子组件
<script>
export default {
  props: ["msg"], // 这行要写,不然下面接收不到
  setup(props) {
    console.log(props)
  },
}
</script>

<template>
  <div class="greetings">
    <h1 class="green">{{ msg }}</h1>
  </div>
</template>

2. 纯 Vue3 写法(语法糖)

父组件变,子组件如下:

<script setup lang="ts">
defineProps<{
  msg: string
}>()
</script>

<template>
  <div class="greetings">
    <h1 class="green">{{ msg }}</h1>
  </div>
</template>

当js逻辑中需要使用传过来的props时,也可以定义一个变量承接一下:

<script setup lang="ts">
const props = defineProps<{
  msg: string
}>()

console.log(props)  // Proxy {msg: '父组件的消息'}
</script>

注意:如果父组件是混合写法,子组件纯 Vue3 写法的话,是接收不到父组件里 data 的属性,只能接收到父组件里 setup 函数里传的属性
如果父组件是纯 Vue3 写法,子组件混合写法,可以通过 props 接收到 data 和 setup 函数里的属性,但是子组件要是在 setup 里接收,同样只能接收到父组件中 setup 函数里的属性,接收不到 data 里的属性。所以官方也不推荐混合写法。

2. attrs传值(父传子)

attrs包含了父作用域里除 class 和 style 除外的非props 属性集合

// 父组件
<script setup lang="ts">
import { reactive } from 'vue'
import child from '../components/Child.vue'
const info = reactive({foo: 111})
console.log(info)
</script>

<template>
  <child msg="父组件的消息" :info="info" />
</template>


// 子组件
<script setup lang="ts">
 import { useAttrs } from "vue"

const attrs = useAttrs()
console.log(attrs)
</script>

<template>
  <div class="greetings">
    <!-- <h1 class="green">{{ msg }} -- {{ info.foo }}</h1> -->
    <h1 class="green">{{ attrs.msg }} -- {{ attrs.info.foo }}</h1>
  </div>
</template>

3. expose/ref (子传父)

父组件获取子组件的属性或者调用子组件方法

// 子组件
<script setup lang="ts">
defineExpose({
    childInfo: '子组件的信息',
    greet() {
        console.log('来自子组件的问候')
    }
})
</script>

// 父组件
<script setup lang="ts">
import { reactive, ref } from 'vue'
import child from '../components/Child.vue'
const info = reactive({foo: 111})
// console.log(info)

const comp = ref()
const handlerClick = () => {
    console.log(comp.value.childInfo) // 获取子组件对外暴露的属性
    comp.value.greet() // 调用子组件对外暴露的方法
}
</script>

<template>
  <child ref="comp" msg="父组件的消息" :info="info" />
  <button @click="handlerClick">按钮</button>
</template>

4. v-model

支持多个数据双向绑定

// Parent.vue
<script setup>
    import child from '../components/Child.vue'
    import { ref, reactive } from "vue"
    const key = ref("1111")
    const value = ref("2222")
</script>
<template>
	<child ref="comp" msg="父组件的消息" :info="info" v-model:key="key" v-model:value="value"/>
</template>

// Child.vue
<template>
     <h1 class="green" @click="handlerClick">{{ attrs.msg }} -- {{ attrs.info.foo }}</h1>
</template>
<script setup>
    const emit = defineEmits(['key', 'value'])
	const handlerClick = () => {
	    emit('update:key', '新的key')
	    emit('update:value', '新的value')
	}
</script>

5. $emit

// 子组件
<script setup lang="ts">
const emit = defineEmits(['childClick'])

const clickMe = () => {
    emit('childClick', '子组件的信息')
}
</script>

<template>
  <div class="greetings">
    <button @click="clickMe">点我</button>
  </div>
</template>

// 父组件
<script setup lang="ts">
const onChildClick = (msg: string) => {
    console.log(msg)
}
</script>

<template>
  <child ref="comp" msg="父组件的消息" :info="info" v-model:key="key" v-model:value="value" @childClick="onChildClick"/>
</template>

在子组件中也可以直接调用$emit方法:

<button @click="$emit('childClick', '子组件的信息')">点我</button>

注意:在 中使用的 $emit 方法不能在组件的

6. provide / inject(跨代传值)

provide: 提供一个值,可以被后代组件注入。
inject: 注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。

// Parent.vue
<script setup>
    import { provide } from "vue"
    provide('parent', 'parent的值')
</script>

// Child.vue
<script setup>
    import { inject } from "vue"
    const value = inject('parent')
	console.log(value)  // parent的值
</script>

7. mitt

Vue3 中没有了 EventBus 跨组件通信,但是现在有了一个替代的方案 mitt.js,原理还是 EventBus

使用步骤:

// 安装mitt
cnpm i mitt -S

封装:

mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt

使用:

// 组件 A
<script setup>
import mitt from './mitt'
const handleClick = () => {
    mitt.emit('handleChange')
}
</script>

// 组件 B 
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
    mitt.off('handleChange',someMethed)
})
</script>

8. vuex / pinia(跨代传值)

pinia之前有篇详细介绍:pinia指南

vuex:

// store/index.js
import { createStore } from "vuex"
export default createStore({
    state:{ count: 1 },
    getters:{
        getCount: state => state.count
    },
    mutations:{
        add(state){
            state.count++
        }
    }
})

// main.js
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store"
createApp(App).use(store).mount("#app")

// Page.vue
<template>
    <div>{{ $store.state.count }}</div>
    <button @click="$store.commit('add')">按钮</button>
</template>

<script setup>
    import { useStore, computed } from "vuex"
    const store = useStore()
    console.log(store.state.count) // 1

    const count = computed(()=>store.state.count) // 响应式,会随着vuex数据改变而改变
    console.log(count) // 1 
</script>