在 Vue3 中,script 脚本存在两种情况。一种是 setup 函数,一种是 <script setup>。而针对这两种不同情况,Vue 也存在 props 和 defineProps 两种接收父组件传递数据的形式。
首先,默认已掌握 Vue2 的父子组件 props 传参,然后进行下面的过程。
setup () {} 函数模式
其实在第一章 拉开序幕的 setup 中,解释了 在 setup 函数中,props 的使用方式。
首先定义父组件,通过reactive 定义响应式数据且返回。然后在父组件中引入子组件 <Child>,将响应式数据 person 对象通过 props 方式传递到子组件中。
<template>
<Child :person="person" />
</template>
<script>
import Child from "./components/Child.vue";
import { reactive } from "vue";
export default {
name: "App",
components: {
Child,
},
setup() {
let person = reactive({
name: "al",
age: 29,
});
return {person};
},
};
</script>
然后定义子组件
<template>
<div>
{{ person }}
</div>
</template>
<script>
export default {
name: 'ChildComponent',
setup(props) {
console.log(props,'props');
}
}
</script>
在 setup 函数中,接收两个参数,第一个就是 props。但是,此时如果直接打印该 props,发现值是一个空对象,且控制台报错。
这是因为,如果使用的是 setup 函数模式,需要像Vue2一样,先通过 props 属性接收,然后才能在 setup 函数中 接收到 props
export default {
name: 'ChildComponent',
props: ['person'],
setup(props) {
console.log(props,'props');
}
}
setup 函数 + ts
上面说的是最基础的 props 数据传递,在Vue3 中更好的支持了ts,以此来限制类型,提高代码质量。所以我们可以通过 ts 来规定参数类型。
// lang 需要配置,表示支持 ts 语法
<script lang="ts">
import Child from "./components/Child.vue";
import { reactive } from "vue";
// 通过 interface 定义接口类型
interface Person {
name: string,
age: number,
}
export default {
name: "App",
components: {
Child,
},
setup() {
// 直接调用 Person 接口,以此来控制类型
let person:Person = reactive({
name: "al",
age: 29,
});
return {person};
},
};
</script>
如果,reactive 定义的数据与 Person 接口存在区别,那么 TS 则会飘红报错,例如:我把对象数据的 name 属性故意写错成 ne属性,vs code 则给了警告
当然,我们可以写的更优雅一点,因为 reactive 本身是可以携带泛型的,所以可以写成
// 通过泛型来规范数据
let person = reactive<Person>({
name: "al",
age: 29,
});
然后再子组件中通过 props 接收时通过断言的形式指定属性的类型,通过defult指定默认值
props: {
person: {
type: Object as () => { name: string; age: number },
default: () => ({
name: "al",
age: 29,
}),
},
},
到了这一步,就算是完成了 setup 函数 + TS 的编写。
<script setup>
在 <script setup> 模式中是不存在 setup 函数的,当然也不能配置 props 属性。<script setup>里面的代码会被编译成组件中 setup()
函数的内容。
那么现在问题来了,我需要怎么接收父组件传递的 props,以及怎么使用该 props 数据呢?
父组件:数据不变,TS类型不变,改为 <script setup> 形式
<template>
<Child :person="person" />
</template>
<script lang="ts" setup>
import Child from "./components/Child.vue";
import { reactive } from "vue";
interface Person {
name: string;
age: number;
}
let person = reactive<Person>({
name: "al",
age: 29,
});
</script>
子组件:Vue3 提供了 一个新的 API -- defineProps 用来接收父组件传递的props 数据。defineProps 是一个函数,接收一个数组参数,数组内部的元素则是父组件传递过来的数据,每个元素都是字符串形式
<template>
<div>
{{ person }}
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
defineProps(['person'])
</script>
展示效果:数据正确展示
父组件传递多个数据
<template>
<Child :person="person" new="newValue" old="oldValue"/>
</template>
子组件只需要在数组中新增元素即可
defineProps(['person','new','old'])
展示效果:
Vue3 中的 defineProps() 函数返回一个 Proxy 对象,该对象内部包含的是 defineProps() 函数 中接收的 props 数据,可以针对于 props 中的数据进行某些代码逻辑判断。例如
<script lang="ts" setup>
import { defineProps } from 'vue'
let x = defineProps(['person','new','old'])
if (x.person.age > 1) {
console.log(x);
}
</script>
展示效果:
到此为止,defineProps() 函数的基本使用就完成了,那如果此时我也想通过 TS 来规范数据类型又需要怎么做呢?
<script setup> + TS
把上面的数据改一下,现在父组件传递的不是一个对象,而是一个数组。数组内部则是个人信息
<template>
<Person :list="persons" />
</template>
<script lang="ts" setup name="App">
import Person from "./components/Child.vue";
import { reactive } from "vue";
let persons = reactive([
{ id: "e98219e12", name: "张三", age: 18 },
{ id: "e98219e13", name: "李四", age: 19 },
{ id: "e98219e14", name: "王五", age: 20 },
]);
</script>
现在需要对父组件中的数据进行 TS 类型限制。我们可以在当前文件中直接写,但是更建议是新开一个文件专门用来写 TS 类型。例如,在 src 目录下新建 types 文件夹,然后新建 index.ts 文件。
// 定义一个接口,限制每个Person对象的格式
export interface Person {
id: string,
name: string,
age: number
}
// 定义一个自定义类型
// export type Persons = Array<Person>
// 简写的自定义类型
export type Persons = Person[]
父组件中根据 接口 和 自定义类型 来规范数据格式
<script lang="ts" setup name="App">
import Person from "./components/Person.vue";
import { reactive } from "vue";
// 引入类型时需要使用 type 声明,之前版本的ts是不需要的
import { type Persons } from "@/types";
// 通过泛型规范 persons 数据类型
let persons = reactive<Persons>([
{ id: "e98219e12", name: "张三", age: 18 },
{ id: "e98219e13", name: "李四", age: 19 },
{ id: "e98219e14", name: "王五", age: 20 },
]);
</script>
在子组件中 通过 defineProps 接收数据
const props = defineProps(['list'])
在子组件中 通过 defineProps 接收数据 + 限制类型:通过泛型限制数据类型
// 引入类型时需要使用 type 声明,之前版本的ts是不需要的
import { type Persons } from "@/types";
const props = defineProps<{list:Persons}>()
在子组件中 通过 defineProps 接收数据 + 限制类型 + 是否必须
// 引入类型时需要使用 type 声明,之前版本的ts是不需要的
import { type Persons } from "@/types";
// 通过 ? 表示该值不是必须值
const props = defineProps<{list?:Persons}>()
在子组件中 通过 defineProps 接收数据 + 限制类型 + 是否必须 + 指定默认值。此时需要引入新的 API -- withDefaults。该 API 也是一个函数,接收两个参数,第一个是 接收数据 + 限制类型 + 是否必须 的集合体,第二个就是指定的默认值。且默认值必须通过函数返回(类似于Vue2中的默认值)。
import {defineProps,withDefaults} from 'vue'
import {type Persons} from '@/types'
let props = withDefaults(
defineProps<{list?:Persons}>(),
{ list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]}
)
总结
在Vue3 中接收父组件传递的 props 数据存在两种方式
- 通过配置式的props ,且使用数组形式 接收父组件传递的数据,然后在 setup 中通过 props 参数获取接收到的数据进行逻辑处理。不处理也可以直接使用。;例如:props : ['a','b','c']
-
通过 <script setup> 形式,使用 defineProps() 来接收父组件传递数据,defineProps() 函数接收一个字符串形式的数组,类似['a','b','c'],数组中每一项就是父组件传递的数据。写在 <script setup> 中的代码最终会被编译到 setup() 函数中。例如:defineProps(['list','a','b'])
通过 TS 限制参数类型 + 指定默认值 + 限制必要性 也存在两种方式
- 通过配置式的props,且使用对象形式 来接收父组件传递的数据,然后通过配置项来限制
props: { person: { // 使用断言控制参数类型 type: Object as () => { name: string; age: number }, // 通过函数返回默认值,一般只有对象或数组才会如此,基本类型直接写值即可 default: () => ({ name: "al", age: 29, }), // 是否必须 isRequied:false }, },
-
通过 <script setup> 形式,使用 defineProps() 来接收父组件传递数据,通过泛型来限制数据类型和是否必须,通过 withDefaults() 设置默认值
let props = withDefaults(defineProps<{list?:Persons}>(),{ list:() =>[{id:'asdasg01',name:'小猪佩奇',age:18}] })