Skip to content

组件之间的传参

父向子传参

父组件

vue

<template>
	<div>
		父级
		<hr>
		<Child :title="name"></Child>
	</div>
</template>

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

let name = ref<string>('Gxx')
</script>

子组件

vue

<script setup lang="ts">
const props = defineProps({
	title: {
		type: String,
		default: '默认值'
	}
})
console.log(props.title)
</script>

<template>
	<div>
		子集
		<div>{{title}}</div>
	</div>
</template>

改用ts泛型的简写形式

ts
const props = defineProps<{
  title: string
}>()

使用ts简写后,增加默认值

ts
withDefaults(defineProps<{
  title: string,
  arr: number[]
}>(), {
  arr: () => [1]
})

子向父传参

自定义事件

子组件

vue

<template>
	<div>
		子集
		<button @click="send">给父组件传值</button>
		// 或者用下面的写法
		<button @click="$emit('on-click','我是子组件的数据','23')">给父组件传值</button>
	</div>
</template>

<script setup lang="ts">
const emit = defineEmits(['on-click'])
const send = () => {
	// 第一个参数是自定义事件的名称
	// 第二个参数是向父组件传递的值,可以有多个,用逗号割开就行
	emit('on-click', '我是子组件的数据', 23)
}
</script>

改用ts泛型简写 defineEmits

ts
const emit = defineEmits<{
  (e: 'on-click', name: string, age: number): void
}>()
const send = () => {
  emit('on-click', '我是子组件的数据', 23)
}

父组件

vue

<template>
	<div>
		父级
		<hr>
		<Child @on-click="getName"></Child>
	</div>
</template>

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

// 子组件传了几个参数,就用几个形参接受
const getName = (name: string, age: number) => {
	//接受子组件的参数
	console.log('我是父组件', name, age)
}
</script>

通过ref来获取子组件的数据

子组件

ts
defineExpose({
  name: 'Gxx',
  open: (arr) => console.log(arr)
})

父组件

vue

<template>
	<div>
		父级
		<hr>
		<Child ref="child"></Child>
	</div>
</template>

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

let arr = reactive<number[]>([1, 2, 3])

const child = ref<InstanceType<typeof Child>>() 
onMounted(() => {
	console.log(child.value)
	console.log(child.value.name);
	// console.log(child.value.open);
	child.value.open(arr)
})
</script>

父子组件的v-model传值

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件, 但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

TIP

prop传值:

Vue2 中的值是 value,Vue3 中的值是 modelValue

自定义事件名称:

Vue2 中的事件名是 input,Vue3 中的值是 update:modelValue(所以Vue3没有.sync)

在父组件中

html

<DomDialog v-model="isDomDialog"></DomDialog>

等同于如下常规写法:

html

<DomDialog v-bind:value="isDomDialog" v-on:input="isDomDialog=$event">
</DomDialog>

或者:

html

<DomDialog :value="isDomDialog" @input="isDomDialog=$event"></DomDialog>

在子组件中的接收与传值

vue

<script>
export default {
	props: {
		value: {type: Boolean,}
	},
	data() {
		return {
			dialogVisible: false,
		}
	},
	watch: {
		value(val) {
			this.dialogVisible = val
		},
	},
	methods: {
		// 关闭弹窗触发
		confrim() {
			this.$emit('input', false)         // 通过 this.$emit() 向父组件传值
		}
	},
}
</script>

在Vue3子组件中

vue

<script setup lang="ts">
defineProps<{
	modelValue: boolean
}>()
const emit = defineEmits(['update:modelValue'])
const close = () => {
	emit('update:modelValue', false)
}
</script>

Vue3支持多个v-model

html

<show v-model:textVal="textVal" v-model="visibleValue"></show>

子组件接收prop参数和自定义事件

ts

const props = defineProps<{
  modelValue: boolean,
  textVal: string,
}>()
const emit = defineEmits(['update:modelValue', 'update:textVal'])

将子组件input输入框的内容回传给父组件

html
<div>内容:<input @change="input" :value="textVal" type="text"></div>
ts
const input = (e: Event) => {
  console.log(e)
  // Event.target 读不到身上的value属性
  const target = e.target as HTMLInputElement
  console.log(target.value)
  emit('update:textVal', target.value)
}

给v-model绑定自定义修饰符

示例: 父组件

html
	<show v-model:textVal.isBt="textVal" v-model="visibleValue"></show>

子组件

ts
const props = defineProps<{
  modelValue: boolean,
  textVal: string,
  textValModifiers?: {
    isBt: boolean
  }
}>()

const input = (e: Event) => {
  console.log(e)
  // Event.target 读不到身上的value属性
  const target = e.target as HTMLInputElement
  console.log(target.value)
  emit('update:textVal', props?.textValModifiers?.isBt ? target.value + '变态' : target.value)
}

Vue2.3 的.sync方法(类似v-model)

它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。

html

<comp :foo.sync="bar"></comp>

会被扩展为:

html

<comp :foo="bar" @update:foo="val => bar = val"></comp>

当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:

js
this.$emit('update:foo', newValue)

兄弟组件

借助父组件传参

例如父组件为App 子组件为A 和 B他两个是同级的

vue

<template>
	<div>
		<A @on-click="getFalg"></A>
		<B :flag="Flag"></B>
	</div>
</template>

<script setup lang='ts'>
import A from './components/A.vue'
import B from './components/B.vue'
import {ref} from 'vue'

let Flag = ref<boolean>(false)
const getFalg = (flag: boolean) => {
	Flag.value = flag;
}
</script>

A 组件派发事件通过App.vue 接受A组件派发的事件然后在Props 传给B组件 也是可以实现的

缺点就是比较麻烦 ,无法直接通信,只能充当桥梁

Event Bus

在Vue2 可以使用$emit 传递 $on监听 emit传递过来的事件

这个原理其实是运用了JS设计模式之发布订阅模式

简易版

ts
type BusClass<T> = {
  emit: (name: T) => void
  on: (name: T, callback: Function) => void
}
type BusParams = string | number | symbol
type List = {
  [key: BusParams]: Array<Function>
}

class Bus<T extends BusParams> implements BusClass<T> {
  list: List
  
  constructor() {
    this.list = {}
  }
  
  emit(name: T, ...args: Array<any>) {
    let eventName: Array<Function> = this.list[name]
    eventName.forEach(ev => {
      ev.apply(this, args)
    })
  }
  
  on(name: T, callback: Function) {
    let fn: Array<Function> = this.list[name] || [];
    fn.push(callback)
    this.list[name] = fn
  }
}

export default new Bus<number>()

然后挂载到Vue config 全局就可以使用了

使用mitt(封装发布订阅的包)

安装依赖

shell
npm i mitt -S

main.ts 文件

ts
import {createApp} from 'vue'
// import './style.css'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// @ts-ignore
import App from './App.vue'

// @ts-ignore
import mitt from "mitt"; 

const Mit = mitt() 

// createApp(App).mount('#app')
const app = createApp(App)

declare module 'vue' { 
  export interface ComponentCustomProperties { 
    $Bus: typeof Mit 
  } 
} 
app.config.globalProperties.$Bus = Mit 
app.use(ElementPlus)
app.mount('#app')

子组件A

vue

<script setup lang="ts">
import {getCurrentInstance} from "vue";

const instance = getCurrentInstance()
const emit = () => {
	instance?.proxy?.$Bus.emit('on-click', 'mitt')
	instance?.proxy?.$Bus.emit('on-click2', 'mitt2')
}
</script>

<template>
	<h1>我是one</h1>
	<button @click="emit">emit</button>
</template>

子组件B

vue

<script setup lang="ts">
import {getCurrentInstance} from "vue";

const instance = getCurrentInstance()
// 创建自定义事件
const Bus = (str) => {
	console.log(str + '=====>B')
}
instance?.proxy?.$Bus.on('on-click', Bus)
//off 取消指定的mitt事件,取消的函数
instance?.proxy?.$Bus.off('on-click', Bus)
// clear  删除全部事件
instance?.proxy?.$Bus.all.clear()

// 监听多条
// instance?.proxy?.$Bus.on('*', (type, str) => {
// 	console.log(type, str + '=====>B')
// })
</script>

<template>
	<h1> 我是Two</h1>
</template>

TIP

getCurrentInstance

Vue3 的 setup 中没有this时需要使用 getCurrentInstance() 来获取。

Vue3 中 getCurrentInstance() 可以用来获取当前组件实例