简言

Props 是一种特别的 attributes,你需要在组件上声明注册,然后才能识别成props,而不是普通的attributes。
本篇主要讲解props的用法,以及传递后的porps值如何保持其响应性。

Props

props声明

如果一个 prop 的名字很长,应使用 camelCase 形式,因为它们是合法的 JavaScript 标识符,可以直接在模板的表达式中使用,也可以避免在作为属性 key 名时必须加上引号。

组合式声明

在组合式写法中,需要使用defineProps 宏进行props声明。

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

defineProps 是一个仅

const props = defineProps(['title'])
console.log(props.title)

选项式声明

在选项式组件中,需要在props属性上声明,它接收一个字符串数组或对象。

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

复杂的props声明

当你需要定制props时,需要使用对象的形式进行声明,这样你可以进行prop传递值校验、prop类型预设、默认值、是否必须传递等配置。
当 prop 的校验失败后,Vue 会抛出一个控制台警告 (在开发模式下)。

defineProps() 宏中的参数不可以访问

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' }
    }
  },
  // 自定义类型校验函数
  // 在 3.4+ 中完整的 props 作为第二个参数传入
  propF: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propG: {
    type: Function,
    // 不像对象或数组的默认,这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})

一些补充细节:

  • 所有 prop 默认都是可选的,除非声明了 required: true。如果是ts的,Vue 会尽最大努力在运行时按照 prop 的类型标注进行编译,例如:defineProps<{ msg: string }> 会被编译为 { msg: { type: String, required: true }}。
  • 除 Boolean 外的未传递的可选 prop 将会有一个默认值 undefined。
  • Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改。
  • 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。

Props传递值

在prop传递时,推荐使用 kebab-case 形式(camelCase形式也行):

<MyComponent greeting-message="hello" />

静态值prop

静态值一般是传递字符串,就像普通attributes赋值一样。

<BlogPost title="My journey with Vue" />

动态值prop

动态值指使用v-bind动态绑定的prop传递值,可以传不同类型的值(字符串、布尔型、数字、对象等)。

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

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

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

<!-- 虽然这个数组是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :comment-ids="[234, 266, 273]" />

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

需要整体绑定多个prop时,可以使用v-bind:

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

等价于:

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

单向数据流

所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。

当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为 JavaScript 的对象和数组是按引用传递,而对 Vue 来说,禁止这样的改动,虽然可能生效,但有很大的性能损耗,比较得不偿失。

在组件中是不能直接修改prop的值的,否则会报错。
若想修改需要对prop的值进行一层转接:

  1. **prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。**在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可。
const props = defineProps(['initialCounter'])

// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
  1. **需要对传入的 prop 值做进一步的转换。**在这种情况中,最好是基于该 prop 值定义一个计算属性:
const props = defineProps(['size'])

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

prop响应性

直接使用一直由响应性的。
prop在解构使用后会失去响应性,在解构使用时我们需要使用toRefs将props包裹,以创建一个和prop同源的响应性值

<script setup lang="ts">
import { toRef, toRefs } from 'vue'

const props = defineProps<{
  msg: string
  age: number
}>()
const msg1 = toRef(() => props.msg)
const { msg, age } = toRefs(props)
console.log(msg.value, age.value)
</script>

想单个的话可以使用toRef,或者直接使用props.prop。
由于toRef和toRefs创建的是和props同源的ref值,所以在组件中也是只读ref值

结语

结束了。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐