VUE3-组件化

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 // string
props.bar // number | undefined
</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) {
// 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 }
});

// 监听 sortId 的变化
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
<!-- Child.vue -->
<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
<!-- Parent.vue -->
<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>