Props
一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props。
setup
在使用 <script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明:
vue
1 2 3 4 5
| <script setup> const props = defineProps(['foo'])
console.log(props.foo) </script>
|
TS
1 2 3 4 5 6 7 8 9
| <script setup lang="ts"> const props = defineProps({ name: { type: String, required: true }, bar: Number })
props.name props.bar </script>
|
注意
props 是 Ref 类型,内部的属性不是。
使用自定义类型
1 2 3 4 5 6 7 8
| interface TGoods { goodsId: number name: string }
const props = defineProps({ goodsDetail: { type: Object as () => TGoods, required: true } })
|
默认值
1 2 3
| const props = defineProps({ maxNum: { type: Number, default: 3 } })
|
非setup
在没有使用 <script setup> 的组件中,props 可以使用 props 选项来声明:
JS
1 2 3 4 5 6 7
| export default { props: ['foo'], setup(props) { console.log(props.foo) } }
|
注意传递给 defineProps() 的参数和提供给 props 选项的值是相同的,两种声明方式背后其实使用的都是 props 选项。
props属性监听
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup> import { defineProps, watch } from 'vue';
const props = defineProps({ sortId: { type: Number, required: true } });
watch(() => props.sortId, (newValue, oldValue) => { console.log(`sortId changed from ${oldValue} to ${newValue}`); }); </script>
|
v-model
v-model 可以在组件上使用以实现双向绑定。
单个属性
从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:
自定义组件中
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup> const model = defineModel<number>()
function update() { model.value++ } </script>
<template> <div>Parent bound v-model is: {{ model }}</div> <button @click="update">Increment</button> </template>
|
父组件可以用 v-model 绑定一个值:
1 2
| <Child v-model="countModel" />
|
defineModel() 返回的值是一个 ref。
它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:
- 它的
.value 和父组件的 v-model 的值同步;
- 当它被子组件变更了,会触发父组件绑定的值一起更新。
defineModel 是一个便利宏。
编译器将其展开为以下内容:
- 一个名为
modelValue 的 prop,本地 ref 的值与其同步;
- 一个名为
update:modelValue 的事件,当本地 ref 的值发生变更时触发。
TS中设置类型
1
| const model = defineModel<number[]>()
|
多个属性
1 2 3 4 5 6 7 8 9
| <script setup> const firstName = defineModel('firstName') const lastName = defineModel('lastName') </script>
<template> <input type="text" v-model="firstName" /> <input type="text" v-model="lastName" /> </template>
|
父组件可以用 v-model 绑定多个值:
1
| <Child v-model:firstName="firstName" v-model:lastName="lastName" />
|
事件
1 2 3 4 5 6 7
| <script setup> const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() { emit('submit') } </script>
|
TS中使用
1 2 3 4 5 6
| <script setup lang="ts"> const emit = defineEmits<{ (e: 'pageSubmit', value: number): void (e: 'drawerClose'): void }>() </script>
|
props、computed、data
这三者都可以用来做数据绑定。
如果要双向绑定或者修改数据建议使用data,props可以修改,但是违反了单向数据流原则,computed 本身不用于双向绑定,它是只读的。
如果想修改props中对象的数据,建议复制 props 对象,然后修改副本,而不是直接修改原始对象。
示例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <template> <div class="TagInputView"> <a-flex wrap="wrap" align="center" gap="small"> <a-tag :bordered="false" v-for="(item, index) in tagValues" :key="index" :closable="item != '所有'" @close.prevent="closeTag(item, index)" > {{ item }} </a-tag> <a-flex align="center" gap="small"> <a-input v-model:value="inputText" style="width: 160px" size="small" /> <a-button size="small" @click="addClick">添加</a-button> </a-flex> </a-flex> </div> </template>
<script lang="ts" setup> import { onMounted, ref, watch } from 'vue'
const inputText = ref('')
const model = defineModel<string>()
const tagValues = ref<string[]>([])
watch( tagValues, () => { model.value = tagValues.value.join(',') }, { deep: true } )
onMounted(() => { if (model.value) { tagValues.value = model.value.split(',') } })
function closeTag(item: string, index: number) { tagValues.value.splice(index, 1) } function addClick() { if (inputText.value) { tagValues.value.push(inputText.value) inputText.value = '' } } </script>
<style src="./TagInputView.less" lang="less" scoped></style>
|
局部注册(推荐)
局部注册的组件仅在当前组件内可用,避免全局污染,是日常开发的首选方式。
步骤
- 导入组件:在使用组件的文件中,通过
import 导入目标组件。
- 直接使用:在
<template> 中直接使用组件名作为标签(组件名建议使用 PascalCase 或 kebab-case,Vue 会自动转换)。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!-- ParentComponent.vue --> <template> <div> <!-- 使用组件(可小写加短横线,或保持 PascalCase) --> <my-component /> <!-- 或 <MyComponent /> --> </div> </template>
<script setup> // 导入组件 import MyComponent from './MyComponent.vue'; // 在 <script setup> 中导入后可直接使用,无需额外注册 </script>
|
注意:在 <script setup> 语法中,导入的组件会自动注册,无需像 Vue2 那样在 components 选项中声明
全局注册
全局注册的组件在整个应用中都可直接使用,无需重复导入,适合通用组件(如按钮、图标等)。
步骤
- 在入口文件(如
main.js 或 main.ts)中导入组件。
- 通过
app.component() 方法注册组件。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { createApp } from 'vue'; import App from './App.vue';
import MyButton from './components/MyButton.vue';
const app = createApp(App);
app.component('MyButton', MyButton);
app.mount('#app');
|
动态组件(根据条件切换)
使用 <component> 标签配合 is 属性,可以动态渲染不同组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div> <!-- is 的值可以是组件名或组件对象 --> <component :is="currentComponent" /> <button @click="toggleComponent">切换组件</button> </div> </template>
<script setup> import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; import { ref } from 'vue';
const currentComponent = ref(ComponentA); // 初始显示 ComponentA
const toggleComponent = () => { currentComponent.value = currentComponent.value === ComponentA ? ComponentB : ComponentA; }; </script>
|