目录
props / emit
父组件使用props向子组件传递数据,子组件则使用事件(events)向父组件反馈信息。
父组件向子组件传递数据
// Parent.vue 发送数据
<child :msg2="msg2"></child>
<script setup lang="ts">
import child from "./child.vue"
import { ref, reactive } from "vue"
const msg2 = ref<string>("这是发送给子组件的消息")
//或者对于复杂类型
const msg2 = reactive<string>(["这是继承组件的消息"])
</script>
// Child.vue 接受数据
<script setup lang="ts">
// 无需引入,直接使用
// import { defineProps } from "vue"
interface Props {
msg1: string
msg2: string
}
const props = withDefaults(defineProps<Props>(), {
msg1: '',
msg2: '',
})
console.log(props) // { msg2: "这是继承组件的消息" }
</script>
子组件向父组件传递数据
// Child.vue 发送
<template>
// 方法1
<button @click="emit('myClick')">Button</button>
// 方法2
<button @click="handleClick">Button</button>
</template>
<script setup lang="ts">
//方法一适用于Vue3.2版本,无需导入
// import { defineEmits } from "vue"
// Corresponding to method one
const emit = defineEmits(["myClick","myClick2"])
// 对应于方法二
const handleClick = ()=>{
emit("myClick", "这是发送给父组件的消息")
}
// 方法二不适合Vue3.2版本,useContext()已被弃用
import { useContext } from "vue"
const { emit } = useContext()
const handleClick = () => {
emit("myClick", "这是发送给父组件的消息")
}
</script>
// Parent.vue 接收
<template>
<child @myClick="onMyClick"></child>
</template>
<script setup lang="ts">
import child from "./child.vue"
const onMyClick = (msg: string) => {
console.log(msg)
}
</script>
provide / inject
这种机制用于开发从父组件到其任何子组件的依赖注入,而不一定是直接的子组件。
- provide:在父组件中可以通过 provide 提供需要向后代组件传递的信息
- inject:在任何后代组件中接收我们要添加到该组件中的数据,无论组件嵌套多深,都可以直接使用。
// Parent.vue
<script setup>
import { provide } from "vue"
provide("name", "张三")
</script>
// Child.vue
<script setup>
import { inject } from "vue"
const name = inject("name")
console.log(name) // 张三
</script>
Pinia
Pinia 是一个全新的 Vue 状态管理库,旨在替代 Vuex。
// main.ts
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
// /store/user.ts
import { defineStore } from 'pinia'
export const userStore = defineStore('user', {
state: () => {
return {
count: 1,
arr: []
}
},
getters: { ... },
actions: { ... }
})
// Page.vue
<template>
<div>{{ store.count }}</div>
</template>
<script lang="ts" setup>
import { userStore } from '../store'
const store = userStore()
// deconstruction
// const { count } = userStore()
</script>
expose / ref
父组件可使用引用直接访问子组件实例或元素。
//父组件代码
<template>
<div>父组件:拿到子组件的message数据:{{ msg }}</div>
<button @click="callChildFn">调用子组件的方法</button>
<hr>
<Child ref="com" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Child from './components/Child.vue'
const com = ref(null) // 通过 模板ref 绑定子组件
const msg = ref('')
onMounted(() => {
// 在加载完成后,将子组件的 message 赋值给 msg
msg.value = com.value.message
})
function callChildFn() {
// 调用子组件的 changeMessage 方法
com.value.changeMessage('你好,中国!')
// 重新将 子组件的message 赋值给 msg
msg.value = com.value.message
}
</script>
//子组件
<template>
<div>子组件:{{ message }}</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('我来自中国!')
function changeMessage(data) {
message.value = data
}
defineExpose({
message,
changeMessage
})
</script>
attr
attrs:包含父作用域的非属性集合,不包括类和样式(用于子组件获取父组件中没有通过props接收的属性)。
// Parent.vue 传值
<child :msg1="msg1" :msg2="msg2" title="子组件"></child>
<script setup>
import child from "./child.vue";
import { ref, reactive } from "vue";
const msg1 = ref("信息1");
const msg2 = ref("信息2");
</script>
// Child.vue 接收
<script setup>
// import { defineProps, useAttrs } from "vue";
// 子组件接收msg1
const props = defineProps({
msg1: String
})
const attrs = useAttrs();
// 因为子组件接收了msg1,所以打印的结果中不会包含msg1, { msg2:"信息1", title: "子组件" }
// 如果子组件没有接收msg1,打印的结果就是 { msg1: "信息1", msg2:"信息12", title: "子组件" }
console.log(attrs);
</script>
v-model
支持多数据块的双向数据绑定
// Parent.vue
<child v-model:key="key" v-model:value="value"></child>
<script setup>
import child from "./child.vue"
import { ref, reactive } from "vue"
const key = ref("1111")
const value = ref("2222")
</script>
// Child.vue
<template>
<button @click="handlerClick">Button</button>
</template>
<script setup>
const emit = defineEmits(["key","value"])
const handlerClick = () => {
emit("update:key", "New key")
emit("update:value", "New value")
}
</script>
mitt.js
在 Vue3 中,用于跨组件通信的 EventBus 已不再可用,Vue 官方推荐使用 mitt 或 tiny-emitter
mitt.js其原理与 EventBus 相同。优点:
- 足够小,仅有 200bytes
- 支持全部事件的监听和批量删除
- 不依赖 Vue 实例,可以跨框架使用,React 或者 Vue,甚至 jQuery 项目都能使用同一套库。
// mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt;
// component A
<script setup>
import mitt from './mitt'
const handleClick = () => {
mitt.emit('handleChange')
}
</script>
// component B
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
mitt.off('handleChange',someMethed)
})
</script>
Slots
插槽允许父组件控制子组件的部分内容。它们对于创建可重复使用且灵活的组件模板非常有用。
默认插槽
// Parent.vue
<FancyButton>
Click me! <!-- slot content -->
</FancyButton>
// Child.vue
<button class="fancy-btn">
<slot></slot> <!-- slot outlet -->
</button>
命名插槽
命名插槽是基于默认插槽的一种分类,可以理解为将内容匹配到相应的占位符。
// Parent.vue
<template>
<Child>
<template v-slot:monkey>
<div>monkey</div>
</template>
<button>点击</button>
</Child>
</template>
// Child.vue
<template>
<div>
<!-- default slots -->
<slot></slot>
<!-- named slots -->
<slot name="monkey"></slot>
</div>
</template>
范围插槽
// Parent.vue
<template>
<!-- v-slot="{scope}" 用于接收从子组件向上传递的数据 -->
<!-- :list="list" 将列表传递给子组件t -->
<Child v-slot="{scope}" :list="list">
<div>
<div>Name: {{ scope.name }}</div>
<div>Occupation: {{ scope.occupation }}</div>
<hr>
</div>
</Child>
</template>
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const list = ref([
{ name: 'Jhon', occupation: 'Thundering'},
...
])
</script>
// Child.vue
<template>
<div>
<!-- 使用 :scope="item" 遍历 -->
<slot v-for="item in list" :scope="item" />
</div>
</template>
<script setup>
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
</script>
Teleport
Teleport 是 Vue3 中新增的特性,它允许你将组件的子节点渲染到 DOM 结构中的任何地方,这样可以实现跨组件的通信。
// Parent.vue
<template>
<teleport to="body">
<div class="popup">
<slot></slot>
</div>
</teleport>
</template>
<style>
.popup {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
// Child.vue
<template>
<div>
<button @click="togglePopup">Toggle Popup</button>
<Popup v-if="showPopup">
<div>Popup Content</div>
</Popup>
</div>
</template>
<script>
import Popup from './Parent.vue';
export default {
components: {
Popup
},
data() {
return {
showPopup: false
};
},
methods: {
togglePopup() {
this.showPopup = !this.showPopup;
}
}
};
</script>