父给子传值props
props的基本使用
只用了JS
// 1. 使用字符串数组的形式
const props = defineProps(['arr'])
// 2. 使用对象的形式。
// 对于以对象形式声明中的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数
const props = defineProps({
arr: {
type: Array,
default: () => [4, 5, 6]
},
str: {
type: String,
default: 'Hello'
}
})
console.log(props.arr, '--', props.str)
如果使用了ts
// 1. 不带默认值
const props = defineProps<{
arr: number[],
str: string
}>()
// 2. 带默认值
const props = withDefaults(defineProps<{
arr: number[],
str: string
}>(), {
arr: () => [3, 2, 1]
})
console.log(props.arr, '--', props.str)
props细节
1. 传递静态数据
// 字符串
<CompA str="hello">
// 传递数值
<CompA :count="2">
// boolean值
<CompA :is-new="false"> // 子组件接收到isNew为false
<CompA is-new> // 子组件接收到isNew为true
// 数组
<CompA :list="[2, 3, 4]">
// 对象
<CompA :count="{
name: 'Li',
sex: 'male'
}">
2. 使用一个对象绑定多个prop
const p = {
name: 'Li',
sex: 'male'
}
<CompA v-bind="p" str="hello"/>
<!-- 等价于 -->
<CompA :name="p.name" :sex="p.sex" str="hello">
注意:在这个例子中,
p被定义为普通数据(非响应式对象),修改p的值,并不会影响子组件的props.name和props.sex.如果希望修改p的值时,子组件的输入也随之改变,可以使用reactive把p变为响应式。
单向数据流
props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。在子组件中去更改一个 prop。如果这么做了,Vue 会在控制台上向抛出警告。 修改props的场景有以下两种
- prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter']) // 计数器只是将
props.initialCounter 作为初始值 // 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
- 需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
const props = defineProps(['size'])
// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
需要对props做进一步转换的时候,不能解构props的值后再赋值,因为解构后失去了与父组件的联系
const { size } = props
// 父组件的size变化时,子组件的normalizedSize不会变
const normalizedSize = computed(() => size.trim().toLowerCase())
prop校验
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
运行时类型检测
校验选项中的 type 可以是下列这些原生构造函数:
StringNumberBooleanArrayObjectDateFunctionSymbol
另外,type 也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配。例如下面这个类:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
defineProps({
author: Person
})
- 传入到
defineProps和defineEmits的选项会从 setup 中提升到模块的作用域。因此,传入的选项不能引用在 setup 作用域中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。
以下例子可以说明:
const b = [9, 9, 9]
const props = defineProps({
localArr: {
type: Array,
default() {
return b;
}
}
})
会出现编译错误:
如果换成以下写法,则不会报错:
var.js文件内容
export const a = [0, 0 ,0]
import { a } from '../assets/js/var.js'
const props = defineProps({
arr: {
type: Array,
default: () => a
}
})
子给父传值defineEmits
子组件:
<button @click="onClick">子向父传值</button>
const emit = defineEmits(['on-click'])
const onClick = () => {
emit('on-click', '子组件参数1', 2)
}
父组件:
<ChildComp str="3" v-bind="p" @on-click="getMessage"/>
const getMessage = (name: string, count: number) => {
console.log('从子组件接收到的值:', name, '--count:', count)
}
如果用的ts,子组件的外抛事件可定义为如下:
const emit = defineEmits<{
(e: 'on-click', name: string, count: number): void
}>()
// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
'on-click': [name: string, count: number]
}>()
const onClick = () => {
emit('on-click', '子组件参数1', 2)
}
父组件访问子组件属性
子组件CompA:
const a = 1
const b = ref(1)
defineExpose({
a,
b
})
父组件:
<CompA ref="compAinstance"></CompA>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const compAinstance = ref<InstanceType<typeof CompA>>()
onMounted(() => {
console.log('compAinstance.a:', compAinstance.value?.a) // 1
console.log('compAinstance.b:', compAinstance.value?.b) // 1,ref自动解包
})
})