父组件通过props向子组件传递数据,子组件通过emits向父组件发送事件。
<!-- Child.vue -->
<template>
<div>
<p>接收到: {{ message }}</p>
<button @click="sendToParent">发送给父组件</button>
</div>
</template>
<script setup>
const props = defineProps(['message'])
const emit = defineEmits(['response'])
function sendToParent() {
emit('response', '来自子组件的消息')
}
</script>
<!-- Parent.vue -->
<template>
<div>
<Child :message="parentMsg" @response="handleResponse" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const parentMsg = ref('来自父组件的消息')
function handleResponse(msg) {
console.log(msg)
}
</script>
用于祖先组件向所有后代组件传递数据,无论层级多深。
<!-- Grandparent.vue -->
<template>
<div>
<Parent />
</div>
</template>
<script setup>
import { provide, ref } from 'vue'
import Parent from './Parent.vue'
const theme = ref('dark')
const user = ref({ name: '张三', role: 'admin' })
// 提供数据
provide('theme', theme)
provide('user', user)
// 提供方法
provide('updateTheme', (newTheme) => {
theme.value = newTheme
})
</script>
<!-- Grandchild.vue -->
<template>
<div>
<p>主题: {{ theme }}</p>
<p>用户: {{ user.name }}</p>
<button @click="changeTheme">切换主题</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入数据
const theme = inject('theme')
const user = inject('user')
const updateTheme = inject('updateTheme')
// 提供默认值
const config = inject('config', { timeout: 3000 })
function changeTheme() {
updateTheme(theme.value === 'dark' ? 'light' : 'dark')
}
</script>
通过ref直接访问子组件实例或DOM元素。
<!-- Child.vue -->
<template>
<div>子组件</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
// 暴露给父组件
defineExpose({
count,
increment() {
count.value++
}
})
</script>
<!-- Parent.vue -->
<template>
<div>
<Child ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
function callChildMethod() {
childRef.value.increment()
console.log(childRef.value.count)
}
onMounted(() => {
// 组件挂载后可以访问
console.log(childRef.value)
})
</script>
Vue3移除了$on、$off等方法,可以使用第三方库或自己实现。
// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()
<!-- ComponentA.vue -->
<template>
<button @click="sendMessage">发送消息</button>
</template>
<script setup>
import { emitter } from './eventBus'
function sendMessage() {
emitter.emit('message', { text: 'Hello from A' })
}
</script>
<!-- ComponentB.vue -->
<template>
<div>{{ receivedMessage }}</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { emitter } from './eventBus'
const receivedMessage = ref('')
function handleMessage(data) {
receivedMessage.value = data.text
}
onMounted(() => {
emitter.on('message', handleMessage)
})
onUnmounted(() => {
emitter.off('message', handleMessage)
})
</script>
用于大型应用的全局状态管理,将在后续课程详细讲解。
// store.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: '张三',
age: 25
}),
actions: {
updateName(newName) {
this.name = newName
}
}
})
<!-- 任何组件都可以访问 -->
<script setup>
import { useUserStore } from './store'
const userStore = useUserStore()
console.log(userStore.name)
userStore.updateName('李四')
</script>
<!-- UserForm.vue -->
<template>
<div>
<input
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
>
<input
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
>
</div>
</template>
<script setup>
defineProps(['firstName', 'lastName'])
defineEmits(['update:firstName', 'update:lastName'])
</script>
<!-- 使用 -->
<template>
<UserForm
v-model:first-name="first"
v-model:last-name="last"
/>
<p>{{ first }} {{ last }}</p>
</template>
<script setup>
import { ref } from 'vue'
import UserForm from './UserForm.vue'
const first = ref('')
const last = ref('')
</script>
<!-- MyButton.vue -->
<template>
<button v-bind="$attrs">
<slot />
</button>
</template>
<script setup>
// 禁用自动继承
defineOptions({
inheritAttrs: false
})
</script>
<!-- 使用 -->
<template>
<MyButton class="primary" @click="handleClick">
点击我
</MyButton>
</template>
<!-- FormInput.vue -->
<template>
<div class="form-input">
<label>{{ label }}</label>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
@blur="$emit('blur')"
>
<span v-if="error" class="error">{{ error }}</span>
</div>
</template>
<script setup>
defineProps(['label', 'modelValue', 'error'])
defineEmits(['update:modelValue', 'blur'])
</script>
<!-- Form.vue -->
<template>
<form @submit.prevent="handleSubmit">
<FormInput
v-model="form.username"
label="用户名"
:error="errors.username"
@blur="validateUsername"
/>
<FormInput
v-model="form.email"
label="邮箱"
:error="errors.email"
@blur="validateEmail"
/>
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { reactive } from 'vue'
import FormInput from './FormInput.vue'
const form = reactive({
username: '',
email: ''
})
const errors = reactive({
username: '',
email: ''
})
function validateUsername() {
errors.username = form.username.length < 3 ? '用户名至少3个字符' : ''
}
function validateEmail() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
errors.email = !emailRegex.test(form.email) ? '邮箱格式不正确' : ''
}
function handleSubmit() {
validateUsername()
validateEmail()
if (!errors.username && !errors.email) {
console.log('提交表单', form)
}
}
</script>