Skip to content

Props

Props 声明

一个组件需要显示的声明它所接收的props,这样Vue才能知道如何正确地解析它们

vue
<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>
javascript
export default {
  props: ['foo'],
  setup(props) {
    // setup() 接收 props 作为第一个参数
    console.log(props.foo)
  },
}

除了使用字符串以外,还可以使用对象的形式

javascript
// 使用 <script setup>
defineProps({
  title: String,
  likes: Number,
})
// 使用 <script>
// 非 <script setup>
export default {
  props: {
    title: String,
    likes: Number,
  },
}

对于对象形式的声明,key时props的名称,而值时与其类型的构造函数

响应式Props结构 (3.5+)

在计算属性或侦听函数中访问props中的属性时,被访问的属性会被跟踪为依赖项

javascript
const { foo } = defineProps(['foo'])

watchEffect(() => {
  // 在 3.5 之前只运行一次
  // 在 3.5+ 中在 "foo" prop 变化时重新执行
  console.log(foo)
})

在 3.4 及以下版本中,foo是一个常量,永远不会改变.而 3.5+ 版本中,foo是一个响应式引用,当foo变化时,watchEffect会重新运行, 上面的代码等价于

javascript
const props = defineProps(['foo'])

watchEffect(() => {
  // `foo` 由编译器转换为 `props.foo`
  console.log(props.foo)
})

此外,还可以使用给JavaScript原生的默认值语法声明props默认值

javascript
const {foo = 'hello'} = defineProps < {foo? : string} > ()

将结构的props传递到函数中

javascript
const { foo } = defineProps(['foo'])

watch(foo /* ... */)

这里并不会按照预期执行,因为它等价与watch(props.foo,...),而props.foo是一个常量,永远不会改变,这里可以将其包装在 getter中来侦听结构的props

javascript
watch(() => foo /* ... */)

传递prop的细节

名字格式

如果名字很长,应该使用cameCase形式,即使合法的JavaScript标识符,也可以直接在模板的表达式中使用,还可以避免作为属性key 时必须添加的引号

javascript
defineProps({
  greetingMessage: String,
})

理论上,在向子组件传递props时,camelCasekebab-case都可以使用,但为了和HTML attribute对其,推荐写成kebab-case的形式

html
<MyComponent greeting-message="hello" />

静态 vs 动态

可以使用v-bind:来动态的绑定props

html
<!-- 根据一个变量的值动态传入 -->
<BlogPost :title="post.title" />

<!-- 根据一个更复杂表达式的值动态传入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

传递不同的值类型

任何类型的值都可以作为props的值被传递

html
<!-- 虽然 `42` 是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :likes="42" />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :likes="post.likes" />
<!-- 仅写上 prop 但不传值,会隐式转换为 `true` -->
<BlogPost is-published />

<!-- 虽然 `false` 是静态的值,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :is-published="false" />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :is-published="post.isPublished" />
<!-- 虽然这个数组是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :comment-ids="post.commentIds" />
<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
/>

<!-- 根据一个变量的值动态传入 -->
<BlogPost :author="post.author" />

使用对象绑定多个 prop

如果想将一个对象的所有属性都作为props传入,可以使用没有参数的v-bind

javascript
const post = {
  id: 1,
  title: 'My Journey with Vue',
}
html
<BlogPost v-bind="post" />

这等价于:

html
<BlogPost :id="post.id" :title="post.title" />

单项数据流

所有的props都遵循着单向数据流,即props会因为父组件中状态的改变将新的状态传递给子组件,但子组件不能改变父组件中的状态, 因为子组件接收的props会随着父组件的状态更新,所以子组件中不应该去修改prop

导致想修改prop的需求通常时以下两种场景:

1.子组件想将prop作为局部数据使用

javascript
const props = defineProps(['initialCounter'])

// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)

2.子组件想将接收到的porp进一步转换

javascript
const props = defineProps(['size'])

// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())

总之,在子组件中不应该直接引用prop进行使用

更改对象/数组类型的props

当对象或数组作为props被传入时,虽然子组件不能修改props的绑定,当是可以修改其内部的值,因为JavaScript的对象和数组时按引用传递, 为了保证单项数据流的规定,应该避免直接修改props的值,而是应该将props 的值赋值给一个本地的数据,然后修改本地的数据,如果需要父组件对数据改变做出回应,子组件应该抛出一个事件来通知父组件

Props 校验

javascript
defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true,
  },
  // 必传但可为 null 的字符串
  propD: {
    type: [String, null],
    required: true,
  },
  // Number 类型的默认值
  propE: {
    type: Number,
    default: 100,
  },
  // 对象类型的默认值
  propF: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    },
  },
  // 自定义类型校验函数
  // 在 3.4+ 中完整的 props 作为第二个参数传入
  propG: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    },
  },
  // 函数类型的默认值
  propH: {
    type: Function,
    // 不像对象或数组的默认,这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    },
  },
})

一些补充细节:

  • 所有的prop默认都是可选的,除非声明了required:true
  • 除了Boolean外的为传递的可选prop都有一个默认值undefined
  • Boolean类型的为传递prop将会被转换为false但是可以通过设置default来更改,比如设置为default:undefined
  • 如果声明了default值,那么无论是未传递还是显示的指明为undefined,都会改为default的值

prop校验失败会,会在开发者模式下的控制台抛出警告

运行时类型检查

校验选项中的type可以时下列这些原生构造函数

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

另外type也可以是自定义的类或构造函数

javascript
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

defineProps({
  author: Person,
})

Boolean 类型转换

声明为Boolean类型的props有特别的类型转换规则 :

javascript
defineProps({
  disabled: Boolean,
})
html
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />

<!-- 等同于传入 :disabled="false" -->
<MyComponent />

当一个 prop 被声明为允许多种类型时,Boolean 的转换规则也将被应用。然而,当同时允许 StringBoolean 时,有一种边缘情况——只有当 Boolean 出现在 String 之前时,Boolean 转换规则才适用:

javascript
// disabled 将被转换为 true
defineProps({
  disabled: [Boolean, Number],
})

// disabled 将被转换为 true
defineProps({
  disabled: [Boolean, String],
})

// disabled 将被转换为 true
defineProps({
  disabled: [Number, Boolean],
})

// disabled 将被解析为空字符串 (disabled="")
defineProps({
  disabled: [String, Boolean],
})