Vue3组件中的八种通信方式

prop


常用场景: 父子传参
父组件

<template>
  <div class="box">
    <h1>组件间通信: props</h1>
    <h3>父组件</h3>
    <div>{{count}}</div>
    <button @click="count++">修改父组件的值</button>
    <Child :count="count" :changeCount="changeCount"></Child>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import Child from './Child.vue'

const count = ref(0)
// 修改父组件的值
const changeCount = (num=1)=>{
  count.value+=num
}
</script>

子组件

<template>
  <div class="box">
    <h3>Child</h3>
    <div>父组件传过来的参数:{{count}}</div>
    <button @click="changeCount(3)">修改父组件的值</button>
  </div>
</template>

<script lang="ts" setup>
  // 接收父组件传过来的参数
defineProps<{
  count:number
  changeCount:(num : number)=>void
}>()

</script>

自定义事件emit


常用场景: 子给父传参
父组件

<template>
  <div class="box">
    <h1>组件间通信: custom_event</h1>
    <!-- 绑定系统内置click -->
    <!-- 元素绑click  系统事件,系统触发,可以拿到事件对象 -->
    <button @click="clickHandler">按钮1</button>
    <!-- 组件绑定系统事件,会自动绑定到子组件的根标签上  d当子组件没有根标签会被当成自定义事件 -->
    <Event1 @click="compClickHandler"></Event1>

    <!-- 自定义事件 -->

    <!-- 组件绑xxx 自定义事件,在子组件中使用emit接收-->
    <Event1 @xxx="xxxHandler" @yyy="yyyHandler"></Event1>

    <!-- 
      组件绑定 系统内置事件 跟vue2刚好相反 vue2需要使用.native ,vue3默认就是
      组件绑定自定义事件,是语法上的差异
    -->
  </div>
</template>

<script lang="ts" setup>
import Event1 from "./Event1.vue";

const clickHandler = (e: Event) => {
  console.log('点击了按钮', e)
}
const compClickHandler = (e: Event) => {
  console.log('组件点击事件', e.target);
  console.log('组件点击事件', e);

}
const xxxHandler = () => {
  console.log('触发了xxx事件');

}
const yyyHandler = (e: string) => {
  console.log('触发了yyy事件', e);

}
</script>

子组件

<template>
  <div class="box">
    <h3>Event1组件</h3>
    <h4>子组件内容</h4>
    <button @click="emit('xxx')">触发xxx事件</button>
    <button @click="emit('yyy', '我爱你')">触发yyy事件</button>
  </div>
</template>

<script lang="ts" setup>
const emit = defineEmits(['xxx', 'yyy'])
</script>

Pubsub


可以用于任意组件间的传参
父组件展示

<template>
  <div class="box">
    <h1>组件间通信: 消息订阅与发布</h1>
    <!-- vue3没有总线,使用Pubsub跨组件通信 -->
    <Child1 />
    <Child2 />
  </div>
</template>

<script lang="ts" setup>
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
</script>

子组件1

<template>
  <div class="box">
    <h3>Child1:接收数据</h3>
    <div>{{message}}</div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import Pubsub from 'pubsub-js'
const message = ref('ooooo')

// 参数一:订阅的类型
// 参数二:我们的参数 
onMounted(() => {
  Pubsub.subscribe('gege', changeMessage)
})
const changeMessage = (type: string, str: string) => {
  message.value = str
}
</script>

子组件2

<template>
  <div class="box">
    <h3>Child2</h3>
    <button @click="Pubsub.publish('gege','啦啦啦')">修改Child1的msg</button>
  </div>
</template>
<script lang="ts" setup>
import Pubsub from 'pubsub-js';
</script>

v-model


常用场景: 表单and组件

父组件

<template>
  <div class="box">
    <h1>组件间通信: v-model</h1>
    <!-- vue3 组件实现v-model是 :modelValue 和 @update:modelValue -->
    <!-- 绑定单个v-model -->
    <CustomInput2 :modelValue="message" @update:modelValue="message=$event"></CustomInput2>
    <CustomInput2 v-model="message"></CustomInput2>
    <h2>绑定多个v-model</h2>
    <!-- 绑定多个v-model -->
    <CustomInput3 v-model="message" v-model:text="text"></CustomInput3>
  </div>
</template>

<script lang="ts" setup>
import CustomInput2 from "./CustomInput2.vue";
import CustomInput3 from "./CustomInput3.vue";
import { ref } from 'vue'
const message = ref('gege')
const text = ref('keai')
</script>

子组件1

<template>
  <div class="box">
    <h4>Child2</h4>
    <input type="text" :value="modelValue" @input="emit('update:modelValue',($event.target as HTMLInputElement).value)">
  </div>
</template>

<script lang="ts" setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

子组件2–绑定多个v-model

<template>
  <div class="box">
    <h4>Child3</h4>
    <input type="text" :value="text" @input="emit('update:text',($event.target as HTMLInputElement).value)">
    <hr>
    <input type="text" :value="modelValue" @input="emit('update:modelValue',($event.target as HTMLInputElement).value)">
  </div>
</template>

<script lang="ts" setup>
defineProps(['modelValue', 'text'])
const emit = defineEmits(['update:modelValue', 'update:text'])
</script>

attrs

父组件

vue3 使用 const attrs = useAttrs(); 来获取到子组件接收的属性和事件,移除了 $listeners
注意: 可以接收到class和style了,defineProps接收过的数据和defineEmits接收过的事件, attrs没有

<template>
  <div class="box">
    <h1>组件间通信: attrs</h1>
    <Child class="qwer" style="height: 80px;" aa="bb" :content="msg" @xxx="xxxHandler"></Child>
  </div>
</template>

<script lang="ts" setup>

import { ref } from 'vue';
import Child from './Child.vue'
const msg = ref('哈哈哈哈')
const xxxHandler = () => {
  console.log('xxx')
}
</script>

子组件

<template>
  <div class="box">
    <h3>Child</h3>
  </div>

</template>

<script lang="ts" setup>
import {useAttrs} from 'vue'
defineProps(['content'])

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

<style lang="less" scoped>
</style>

如图:被props接收过的参数content的数据没有 其他style class 以及事件都能够被接收到
Vue3组件中的八种通信方式_第1张图片

$attr $parent


应用场景


$refs:父组件访问子组件


$parent:子组件访问父组件
父组件

<template>
  <div class="box">
    <h1>组件间通信: $ref & $parent</h1>

    <p>BABA有存款:{{ money }}</p>
    <button @click="borrowMoneyFromXM(100)">找小明借钱100</button><br>
    <br>
    <Son ref="sonRef" />

    <br>
    <Daughter ref="dauRef" />
  </div>
</template>

<script lang="ts" setup>
import Son from './Son.vue'
import Daughter from './Daughter.vue'
import { ref } from 'vue'
const money = ref(1000)
const btnRef = ref();
const sonRef = ref() // 获取组件实例: const声明的sonRef变量必须和 组件标签上ref的属性值一样
const dauRef = ref()
const borrowMoneyFromXM = (num :number)=>{
  money.value+=num //父组件加钱
  sonRef.value.money-=num //子组件减钱
//  父组件加钱
}
// 直接获取子组件改子组件数据选在不允许了,需要让子组件自己说明哪些数据是外部可以改动的
defineExpose({
  money
})

</script>

子组件

<template>
  <div class="box">
    <h3>儿子小明: 有存款: {{ money }}</h3>
    <button @click="giveMoney(50,$parent)">BABA: 50</button>
  </div>
</template>


<script lang="ts" setup>
import { ref } from 'vue'
const money = ref(20000)

const giveMoney = (num :number,parent:any)=>{
  money.value-=num
  console.log('parent',parent)
  parent.money+=num
}
defineExpose({
  money
})
</script>

defineExpose说明

// setup 挂载到 script (ref默认是关闭的) 是不能直接使用ref 取到子组件的方法和变量
// 子组件需要使用defineExpose导出,父组件才能拿到
defineExpose({
  money
})

provide + inject


应用场景


爷爷组件与孙子组件之间互相传参


爷爷组件

<template>
  <div class="box">
    <h1>组件间通信: provide + inject</h1>

    <p>content1: {{ content1 }}</p>
    <button @click="content1 += '--'">更新content1</button>
    <p>content2.name: {{ content2.name }}</p>
    <button @click="content2.name += '~~'">更新content2对象内的name</button>

    <Child />
  </div>
</template>

<script lang="ts" setup>
import { provide, reactive, ref } from 'vue';
import Child from './Child.vue'

const content1 = ref('jack')
const content2 = reactive({
  name: 'tom',
})
const changeContent1 = () => {
  content1.value = '杰克';
}
const changeContent2 = () => {
  content2.name = '汤姆';
}
provide('content1',content1)
provide('content2',content2)
provide('changeContent1',changeContent1)
provide('changeContent2',changeContent2)


</script>

孙子组件

<template>
  <div class="box">
    <h1>组件间通信: provide + inject</h1>

    <p>content1: {{ content1 }}</p>
    <button @click="content1 += '--'">更新content1</button>
    <p>content2.name: {{ content2.name }}</p>
    <button @click="content2.name += '~~'">更新content2对象内的name</button>

    <Child />
  </div>
</template>

<script lang="ts" setup>
import { provide, reactive, ref } from 'vue';
import Child from './Child.vue'

const content1 = ref('jack')
const content2 = reactive({
  name: 'tom',
})
const changeContent1 = () => {
  content1.value = '杰克';
}
const changeContent2 = () => {
  content2.name = '汤姆';
}
provide('content1',content1)
provide('content2',content2)
provide('changeContent1',changeContent1)
provide('changeContent2',changeContent2)


</script>

slot


普通插槽 直接使用即可


具名插槽 具有名字的插槽


作用域插槽 在标签上绑定属性


绑定的数据在传递中是可以获取到的


具名插槽

    <List>
      <template v-slot:header>
        <div>header-text</div>
      </template>
    </List>
<div>
  <slot name="header">哈哈哈哈哈哈哈</slot>
</div>

作用域插槽

<template>
<div>
    <List :data="todos">
      <template v-slot="{ row, $index }">
        <span :style="{ color: $index % 2 === 1 ? 'blue' : 'green' }">{{ $index + 1 }}--{{ row.text }}</span>
      </template>
    </List>
</div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import List from './List.vue'
import type { Users } from './types';

const todos = ref<Users>([
  { id: 1, text: 'AAA', isComplete: false },
  { id: 2, text: 'BBB', isComplete: true },
  { id: 3, text: 'CCC', isComplete: false },
  { id: 4, text: 'DDD', isComplete: false },
])
</script>
<template>
  <div class="box">
    <ul>
      <li v-for="(item, index) in data" :key="item.id">
        <slot :row="item" :$index="index"></slot>
      </li>
    </ul>
  </div>
</template>

<script lang="ts" setup>
import type { Users } from './types';
interface Props {
  data: Users;
}
defineProps<Props>()
</script>

你可能感兴趣的:(vue,javascript,vue.js,前端)