跳转到内容

属性

本页面假设您已经阅读了组件基础。如果您是组件的新手,请先阅读。

属性声明

Vue 组件需要显式声明属性,这样 Vue 才知道哪些外部传递给组件的属性应该被视为透传属性(将在其专属部分中讨论)。

<script setup> 模板中使用的 SFC,可以使用 defineProps() 宏声明属性

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

console.log(props.foo)
</script>

在非 <script setup> 组件中,使用 props 选项声明属性

js
export default {
  props: ['foo'],
  setup(props) {
    // setup() receives props as the first argument.
    console.log(props.foo)
  }
}

注意传递给 defineProps() 的参数与传递给 props 选项的值相同:两种声明方式共享相同的属性选项 API。

使用 props 选项声明属性

js
export default {
  props: ['foo'],
  created() {
    // props are exposed on `this`
    console.log(this.foo)
  }
}

除了使用字符串数组声明属性外,我们还可以使用对象语法

js
export default {
  props: {
    title: String,
    likes: Number
  }
}
js
// in <script setup>
defineProps({
  title: String,
  likes: Number
})
js
// in non-<script setup>
export default {
  props: {
    title: String,
    likes: Number
  }
}

在对象声明语法中,每个属性的键是属性名,而值应该是预期类型的构造函数。

这不仅会记录您的组件,还会在浏览器控制台中警告其他使用您的组件的开发者,如果他们传递了错误类型。我们将在本页面的下方进一步讨论关于属性验证的更多细节。

如果您正在使用 TypeScript 与 <script setup>,还可以使用纯类型注解声明属性

vue
<script setup lang="ts">
defineProps<{
  title?: string
  likes?: number
}>()
</script>

更多详情:为组件属性进行类型定义

响应式属性解构

Vue 的反应性系统根据属性访问跟踪状态使用情况。例如,当您在计算属性或观察者中访问 props.foo 时,foo 属性被跟踪为依赖项。

所以,给定以下代码

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

watchEffect(() => {
  // runs only once before 3.5
  // re-runs when the "foo" prop changes in 3.5+
  console.log(foo)
})

在3.4版本及以下,foo是一个实际的常量,将永远不会改变。从3.5版本开始,Vue的编译器会在同一<script setup>块中访问从defineProps解构的变量时自动添加props.。因此,上述代码等同于以下代码

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

watchEffect(() => {
  // `foo` transformed to `props.foo` by the compiler
  console.log(props.foo)
})

此外,您可以使用JavaScript的原生默认值语法来声明props的默认值。这在使用基于类型的props声明时尤其有用。

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

如果您希望在使用IDE时在解构props和普通变量之间有更多的视觉区分,Vue的VSCode扩展提供了启用解构props的inlay-hints的设置。

将解构props传递给函数

当我们将解构prop传递给一个函数时,例如

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

watch(foo, /* ... */)

这不会按预期工作,因为它等同于watch(props.foo, ...) - 我们传递的是一个值而不是reactive数据源到watch。实际上,Vue的编译器会捕获此类情况并抛出警告。

类似于我们如何使用watch(() => props.foo, ...)来监听普通prop,我们也可以通过包装在getter中来监听解构prop。

js
watch(() => foo, /* ... */)

此外,当我们需要在传递解构prop到外部函数的同时保留reactivity时,这是推荐的方法。

js
useComposable(() => foo)

外部函数可以在需要跟踪提供的prop变化时调用getter(或使用toValue进行标准化)。

Prop传递详情

Prop名称大小写

我们使用camelCase来声明长prop名称,因为这样可以避免在使用它们作为属性键时使用引号,并允许我们在模板表达式中直接引用它们,因为它们是有效的JavaScript标识符。

js
defineProps({
  greetingMessage: String
})
js
export default {
  props: {
    greetingMessage: String
  }
}
template
<span>{{ greetingMessage }}</span>

技术上,在将props传递给子组件时也可以使用camelCase(除了在in-DOM模板中)。然而,惯例是在所有情况下都使用kebab-case,以与HTML属性保持一致。

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

在可能的情况下,我们使用PascalCase作为组件标签,因为它可以通过区分Vue组件和原生元素来提高模板的可读性。然而,在传递props时使用camelCase并没有太大的实际好处,因此我们选择遵循每种语言的约定。

静态与动态props

到目前为止,您已经看到了像下面这样的静态值传递的props

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

您也看到了使用v-bind或其:快捷方式动态分配的props,例如

template
<!-- Dynamically assign the value of a variable -->
<BlogPost :title="post.title" />

<!-- Dynamically assign the value of a complex expression -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

传递不同类型值

在上面的两个示例中,我们传递了字符串值,但可以传递任何类型的值给prop。

数字

template
<!-- Even though `42` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.       -->
<BlogPost :likes="42" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :likes="post.likes" />

布尔值

template
<!-- Including the prop with no value will imply `true`. -->
<BlogPost is-published />

<!-- Even though `false` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.          -->
<BlogPost :is-published="false" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :is-published="post.isPublished" />

数组

template
<!-- Even though the array is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.            -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :comment-ids="post.commentIds" />

对象

template
<!-- Even though the object is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.             -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :author="post.author" />

使用对象绑定多个属性

如果您想将对象的全部属性作为props传递,可以使用不带参数的v-bind(使用v-bind而不是:prop-name)。例如,给定一个post对象

js
export default {
  data() {
    return {
      post: {
        id: 1,
        title: 'My Journey with Vue'
      }
    }
  }
}
js
const post = {
  id: 1,
  title: 'My Journey with Vue'
}

以下模板

template
<BlogPost v-bind="post" />

将等同于

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

单向数据流

所有props在子组件属性和父组件属性之间形成单向绑定:当父组件属性更新时,它会流向子组件,但不会反过来。这可以防止子组件意外地修改父组件的状态,这可能会使您的应用程序的数据流更难以理解。

此外,每当父组件更新时,子组件中的所有props都会刷新为最新值。这意味着您不应该在子组件内部尝试修改prop。如果您这样做,Vue将在控制台警告您

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

// ❌ warning, props are readonly!
props.foo = 'bar'
js
export default {
  props: ['foo'],
  created() {
    // ❌ warning, props are readonly!
    this.foo = 'bar'
  }
}

通常有两种情况下可能会诱使您修改prop

  1. 该prop用于传递初始值;子组件之后想将其用作本地数据属性。在这种情况下,最好定义一个本地数据属性,该属性使用prop作为其初始值

    js
    const props = defineProps(['initialCounter'])
    
    // counter only uses props.initialCounter as the initial value;
    // it is disconnected from future prop updates.
    const counter = ref(props.initialCounter)
    js
    export default {
      props: ['initialCounter'],
      data() {
        return {
          // counter only uses this.initialCounter as the initial value;
          // it is disconnected from future prop updates.
          counter: this.initialCounter
        }
      }
    }
  2. 该prop是一个需要转换的原始值。在这种情况下,最好定义一个使用prop值的计算属性

    js
    const props = defineProps(['size'])
    
    // computed property that auto-updates when the prop changes
    const normalizedSize = computed(() => props.size.trim().toLowerCase())
    js
    export default {
      props: ['size'],
      computed: {
        // computed property that auto-updates when the prop changes
        normalizedSize() {
          return this.size.trim().toLowerCase()
        }
      }
    }

修改对象/数组Props

当对象和数组作为props传递时,尽管子组件不能修改prop绑定,但它将能够修改对象或数组的嵌套属性。这是因为JavaScript中的对象和数组是通过引用传递的,Vue阻止这种修改是不合理的昂贵。

这种修改的主要缺点是它允许子组件以父组件不明显的方式影响父状态,这可能会使未来对数据流的推理更加困难。作为一个最佳实践,除非父组件和子组件在设计上紧密耦合,否则应避免此类修改。在大多数情况下,子组件应发出事件,让父组件执行修改。

Prop验证

组件可以指定其props的要求,例如您已经看到的类型。如果未满足要求,Vue将在浏览器JavaScript控制台中警告您。这对于开发打算供他人使用的组件非常有用。

要指定prop验证,您可以提供一个包含验证要求的对象到defineProps()props选项,而不是字符串数组。例如

js
defineProps({
  // Basic type check
  //  (`null` and `undefined` values will allow any type)
  propA: Number,
  // Multiple possible types
  propB: [String, Number],
  // Required string
  propC: {
    type: String,
    required: true
  },
  // Required but nullable string
  propD: {
    type: [String, null],
    required: true
  },
  // Number with a default value
  propE: {
    type: Number,
    default: 100
  },
  // Object with a default value
  propF: {
    type: Object,
    // Object or array defaults must be returned from
    // a factory function. The function receives the raw
    // props received by the component as the argument.
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // Custom validator function
  // full props passed as 2nd argument in 3.4+
  propG: {
    validator(value, props) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // Function with a default value
  propH: {
    type: Function,
    // Unlike object or array default, this is not a factory
    // function - this is a function to serve as a default value
    default() {
      return 'Default function'
    }
  }
})

提示

defineProps()参数内的代码不能访问在<script setup>中声明的其他变量,因为在编译时整个表达式被移动到外部函数作用域。

js
export default {
  props: {
    // Basic type check
    //  (`null` and `undefined` values will allow any type)
    propA: Number,
    // Multiple possible types
    propB: [String, Number],
    // Required string
    propC: {
      type: String,
      required: true
    },
    // Required but nullable string
    propD: {
      type: [String, null],
      required: true
    },
    // Number with a default value
    propE: {
      type: Number,
      default: 100
    },
    // Object with a default value
    propF: {
      type: Object,
      // Object or array defaults must be returned from
      // a factory function. The function receives the raw
      // props received by the component as the argument.
      default(rawProps) {
        return { message: 'hello' }
      }
    },
    // Custom validator function
    // full props passed as 2nd argument in 3.4+
    propG: {
      validator(value, props) {
        // The value must match one of these strings
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // Function with a default value
    propH: {
      type: Function,
      // Unlike object or array default, this is not a factory
      // function - this is a function to serve as a default value
      default() {
        return 'Default function'
      }
    }
  }
}

其他详细信息

  • 默认情况下,所有prop都是可选的,除非指定了required: true

  • 除了Boolean之外,缺失的可选属性将具有undefined的值。

  • 缺失的Boolean属性将被转换为false。您可以通过为其设置默认值来更改此行为,即:default: undefined,以使其作为非布尔属性。

  • 如果指定了默认值,则在解析的属性值为undefined时将使用该默认值 - 这包括属性缺失或显式传递undefined值的情况。

当属性验证失败时,Vue将在开发构建中生成控制台警告。

如果使用基于类型的属性声明 ,Vue将尽可能将类型注解编译成等效的运行时属性声明。例如,defineProps<{ msg: string }>将被编译成{ msg: { type: String, required: true }}

注意

请注意,属性验证是在创建组件实例之前进行的,因此实例属性(例如datacomputed等)不会在defaultvalidator函数内部可用。

运行时类型检查

type可以是以下原生构造函数之一

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

此外,type也可以是自定义类或构造函数,并且将通过instanceof检查进行断言。例如,给定以下类

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

您可以使用它作为属性的类型

js
defineProps({
  author: Person
})
js
export default {
  props: {
    author: Person
  }
}

Vue将使用instanceof Person来验证author属性值是否确实是Person类的实例。

可空类型

如果类型是必需的但可空的,可以使用包含null的数组语法

js
defineProps({
  id: {
    type: [String, null],
    required: true
  }
})
js
export default {
  props: {
    id: {
      type: [String, null],
      required: true
    }
  }
}

请注意,如果type仅为null而不使用数组语法,它将允许任何类型。

布尔转换

具有Boolean类型的属性具有特殊的转换规则,以模仿原生布尔属性的行为。给定以下声明的<MyComponent>

js
defineProps({
  disabled: Boolean
})
js
export default {
  props: {
    disabled: Boolean
  }
}

组件可以使用如下方式使用

template
<!-- equivalent of passing :disabled="true" -->
<MyComponent disabled />

<!-- equivalent of passing :disabled="false" -->
<MyComponent />

当一个属性声明为允许多个类型时,Boolean的转换规则也将适用。但是,当同时允许StringBoolean时,存在一个边缘情况 - 只有当布尔值出现在字符串之前时,布尔转换规则才适用

js
// disabled will be casted to true
defineProps({
  disabled: [Boolean, Number]
})

// disabled will be casted to true
defineProps({
  disabled: [Boolean, String]
})

// disabled will be casted to true
defineProps({
  disabled: [Number, Boolean]
})

// disabled will be parsed as an empty string (disabled="")
defineProps({
  disabled: [String, Boolean]
})
js
// disabled will be casted to true
export default {
  props: {
    disabled: [Boolean, Number]
  }
}

// disabled will be casted to true
export default {
  props: {
    disabled: [Boolean, String]
  }
}

// disabled will be casted to true
export default {
  props: {
    disabled: [Number, Boolean]
  }
}

// disabled will be parsed as an empty string (disabled="")
export default {
  props: {
    disabled: [String, Boolean]
  }
}
属性已加载