大家好,欢迎来到今天的Vue 3源码深度解析小课堂!今天咱们要扒一扒Vue 3里那个神秘又重要的setup
函数,特别是它的两个参数:props
和context
。别怕,不会让你对着密密麻麻的代码发呆,咱们用大白话+代码示例,把它们的老底都给揭了!
开场白:setup
,你的组件好帮手
setup
函数是Vue 3组件里的一块宝地,咱们可以在这里面做数据初始化、响应式状态管理、事件处理等等。它就像是组件的“大脑”,负责指挥整个组件的运作。而props
和context
,就是这个大脑里的两个重要助手,帮助我们获取外部信息和操作组件。
第一部分:props
,数据通道的守护者
props
,顾名思义,就是properties的缩写,代表着父组件传递给子组件的数据。它就像一条数据通道,让父组件可以把信息传递给子组件。
1.1 props
的定义和类型校验
在Vue 3中,我们可以在组件选项里用props
选项来定义组件可以接收哪些props
。它支持两种写法:数组形式和对象形式。
- 数组形式(简单粗暴):
// MyComponent.vue
export default {
props: ['message', 'count'],
setup(props) {
console.log(props.message); // 可以访问到父组件传递的 message
console.log(props.count); // 可以访问到父组件传递的 count
return {};
}
}
// ParentComponent.vue
<template>
<MyComponent message="Hello" :count="123" />
</template>
这种写法简单,但是缺少类型校验,容易出错。
- 对象形式(更严谨):
// MyComponent.vue
export default {
props: {
message: {
type: String, // 限制类型为字符串
required: true, // 必须传递
default: 'Default Message' // 默认值
},
count: {
type: Number, // 限制类型为数字
default: 0,
validator(value) { // 自定义校验器
return value >= 0; // 必须大于等于0
}
}
},
setup(props) {
console.log(props.message);
console.log(props.count);
return {};
}
}
// ParentComponent.vue
<template>
<MyComponent :message="parentMessage" :count="parentCount" />
</template>
<script>
export default {
data() {
return {
parentMessage: "Hello from Parent",
parentCount: 5
};
}
};
</script>
对象形式可以定义props
的类型、是否必传、默认值、自定义校验器等等,让我们的代码更加健壮。
1.2 props
的底层实现
Vue 3在组件初始化的时候,会解析组件选项里的props
选项,并根据定义的类型、默认值、校验器等信息,创建一个props
对象。这个props
对象会被传递给setup
函数作为第一个参数。
简单来说,Vue 3内部会做以下事情:
- 定义
props
选项: 解析组件选项中的props
字段(数组或对象)。 - 创建
props
对象: 根据props
定义,创建一个响应式的props
对象。 - 类型校验和默认值: 对传入的
props
进行类型校验,设置默认值(如果定义了)。 - 传递给
setup
: 将创建好的props
对象作为第一个参数传递给setup
函数。
1.3 props
的响应式
props
对象是响应式的,这意味着当父组件更新传递给子组件的props
时,子组件的setup
函数中接收到的props
也会自动更新,视图也会随之重新渲染。
// ParentComponent.vue
<template>
<MyComponent :message="parentMessage" />
<button @click="updateMessage">Update Message</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const parentMessage = ref("Hello from Parent");
const updateMessage = () => {
parentMessage.value = "New Message from Parent";
};
return {
parentMessage,
updateMessage
};
}
};
</script>
// MyComponent.vue
export default {
props: {
message: {
type: String,
required: true
}
},
setup(props) {
console.log("MyComponent props.message:", props.message); // 每次 parentMessage 更新都会触发
return {};
}
};
在这个例子中,当父组件的parentMessage
改变时,子组件MyComponent
的setup
函数会重新执行,打印出新的props.message
值。
第二部分:context
,组件的工具箱
context
对象是setup
函数的第二个参数,它提供了一组有用的属性和方法,让我们可以操作组件的状态、事件、插槽等等。
2.1 context
的组成
context
对象主要包含以下三个属性:
属性 | 类型 | 描述 |
---|---|---|
attrs |
对象 | 包含组件上所有没有被props 声明的attribute绑定 (相当于 $attrs )。 |
emit |
函数 | 用于触发自定义事件 (相当于 $emit )。 |
slots |
对象 | 包含所有传递给组件的插槽 (相当于 $slots )。 |
expose |
函数 | 用于显式地暴露组件内部的状态给父组件。 只有在使用 <script setup> 的单文件组件中,才需要使用 defineExpose 编译器宏。在其他情况下,所有返回的响应式状态都会自动暴露。 |
2.2 context.attrs
context.attrs
包含了组件上所有没有被props
声明的attribute绑定。 比如:
// ParentComponent.vue
<template>
<MyComponent message="Hello" data-id="123" :style="{ color: 'red' }" />
</template>
// MyComponent.vue
export default {
props: ['message'],
setup(props, context) {
console.log(props.message); // "Hello"
console.log(context.attrs); // { data-id: "123", style: { color: 'red' } }
return {};
}
}
context.attrs
对于创建高阶组件或者需要访问未声明的attribute绑定非常有用。
2.3 context.emit
context.emit
用于触发自定义事件,让子组件可以向父组件传递信息。
// MyComponent.vue
export default {
setup(props, context) {
const handleClick = () => {
context.emit('custom-event', 'Data from child'); // 触发 custom-event 事件
};
return {
handleClick
};
},
template: `
<button @click="handleClick">Click Me</button>
`
}
// ParentComponent.vue
<template>
<MyComponent @custom-event="handleCustomEvent" />
</template>
<script>
export default {
methods: {
handleCustomEvent(data) {
console.log('Received:', data); // "Received: Data from child"
}
}
};
</script>
context.emit
的第一个参数是事件名称,后面的参数是传递给父组件的数据。
2.4 context.slots
context.slots
包含了所有传递给组件的插槽,让我们可以灵活地渲染父组件传递的内容。
// MyComponent.vue
export default {
setup(props, context) {
return () => (
<div>
<p>MyComponent Content</p>
{context.slots.default ? context.slots.default() : <p>No Default Slot</p>}
{context.slots.header ? <div>Header: {context.slots.header()}</div> : null}
</div>
);
}
}
// ParentComponent.vue
<template>
<MyComponent>
<template v-slot:default>
<p>Default Slot Content</p>
</template>
<template v-slot:header>
<h1>Header Content</h1>
</template>
</MyComponent>
</template>
context.slots
是一个对象,包含了所有具名插槽和默认插槽。我们可以通过插槽名称来访问对应的插槽内容。
2.5 context.expose
(仅用于 <script setup>
)
context.expose
函数用于显式地暴露组件内部的状态给父组件。 在 <script setup>
语法糖中,默认情况下,组件内部的任何变量都是私有的,不会暴露给父组件。 如果需要父组件访问子组件的某些状态或方法,就需要使用 defineExpose
宏(在 <script setup>
内部)或者 context.expose
(在非 <script setup>
中)。
// MyComponent.vue (非 <script setup>)
<script>
import { ref } from 'vue';
export default {
setup(props, context) {
const count = ref(0);
const increment = () => {
count.value++;
};
context.expose({
count,
increment
});
return {
count,
increment
}
},
template: `
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
};
</script>
// ParentComponent.vue
<template>
<MyComponent ref="myComponent" />
<button @click="accessChild">Access Child</button>
</template>
<script>
import { ref, onMounted } from 'vue';
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
setup() {
const myComponent = ref(null);
const accessChild = () => {
console.log(myComponent.value.count); // 访问子组件暴露的 count
myComponent.value.increment(); // 调用子组件暴露的 increment 方法
};
onMounted(() => {
console.log(myComponent.value); // 此时可以访问到子组件
});
return {
myComponent,
accessChild
};
}
};
</script>
2.6 context
的底层实现
Vue 3在组件初始化的时候,会创建一个context
对象,并将attrs
、emit
和slots
等属性添加到该对象上。这个context
对象会被传递给setup
函数作为第二个参数。
第三部分:props
+ context
= 组件的无限可能
props
和context
是setup
函数的两个重要参数,它们一起为我们提供了操作组件的强大能力。
props
让我们接收父组件传递的数据,实现组件的定制化。context
让我们访问组件的attribute绑定、触发自定义事件、渲染插槽内容,实现组件的交互和扩展。
掌握了props
和context
,就等于掌握了setup
函数的核心,也就掌握了Vue 3组件的灵魂。
总结:props
和context
,你的Vue 3好伙伴
props
和context
是Vue 3组件中不可或缺的两个助手。它们帮助我们构建灵活、可复用、可扩展的组件。希望通过今天的讲解,大家能够对props
和context
有更深入的理解,并在实际开发中灵活运用它们,写出更加优雅的Vue 3代码!
今天的课程就到这里,谢谢大家!下次再见!