Vue props属性的用法(超级详细)
使用 props 可以方便地向组件传递数据。从功能上讲,props 也可以称为组件的外部属性,通过 props 的传参差异,组件可以有很强的灵活性和扩展性。
例如,在 Vue 组件中,如果一个自定义组件需要通过 props 接收外部传入的数值,但调用方错误地传递了一个字符串类型的数据,这可能导致组件内部出现错误。
为了增强类型安全性,Vue 允许在定义组件的 props 时添加约束,对其类型、默认值、是否可选等进行配置。这有助于确保组件接收到正确类型的数据,并在数据不符合预期时提供明确的错误信息。
新建一个名为 demo.props.html 的测试文件。在其中编写如下核心代码:
例如,下面的组件的使用方式,最终页面渲染的计数结果将不是预期的:
虽然 count 属性的本意是作为组件内部计数的初始值,但调用方不一定能完全理解组件内部的逻辑,调用此组件时极有可能会传递非数值类型的数据,例如:

图 1 组件渲染示例
可以看到,其渲染结果并不正常。在 Vue 中,我们可以对定义的 props 进行约束来显式地指定其类型。当将组件的 props 配置项配置为列表时,表示当前定义的属性没有任何约束控制,如果将其配置为对象,则可以进行更多约束设置。
修改上面代码中 props 的定义如下:
如果只需要指定属性的类型,而不需要指定更加复杂的性质,可以使用如下方式定义:
如果一个属性可能是多种类型,可以如下定义:
在对属性的默认值进行配置时,如果默认值的获取方式比较复杂,也可以将其定义为函数,函数执行的结果会被作为当前属性的默认值,示例代码如下:
Vue 中 props 的定义支持进行自定义验证。以上述代码为例,假设组件内需要接收的 count 属性的值必须大于数值 10,则可以通过自定义验证函数实现:
props 的这种只读性是Vue单向数据流特性的一种体现。对于所有的外部属性,props 都只允许父组件的数据流动到子组件中,子组件的数据则不允许流向父组件。因此,在组件内部修改 props 的值是无效的。
以计数器页面为例,如果我们定义 props 只是为了设置组件某些属性的初始值,完全可以使用计算属性来进行桥接,也可以将外部属性的初始值映射到组件的内部属性上,示例代码如下:
新建一个名为 demo.provide.html 的测试文件,在其中编写如下核心示例代码:
运行上述代码,页面效果如下图所示:

图 2 自定义列表组件
运行上述代码本身没有什么问题,烦琐的地方在于 my-label 组件中需要使用到 my-list 组件中的 count 属性,通过使用 my-item 组件,数据才能顺利进行传递。随着组件嵌套层数的增多,数据的传递将越来越复杂。对于这种场景,我们可以使用数据注入的方式来跨层级进行数据传递。
所谓数据注入,是指父组件可以向其所有子组件提供数据,不论在层级结构上此子组件的层级有多深。以上面的代码为例,my-label 组件可以跳过 my-item 组件,直接使用 my-list 组件中提供的数据。
实现数据注入,需要使用 provide 与 inject 两个函数,提供数据的父组件需要设置 provide 配置项来提供数据,子组件需要设置 inject 配置项来获取数据。修改上述代码如下:
对props属性进行验证
JavaScript 是一种非常灵活且自由的编程语言。在 JavaScript 中定义函数时,无需指定参数的类型。这种编程风格虽然为开发者提供了极大的便利,但同时也牺牲了一定的安全性。例如,在 Vue 组件中,如果一个自定义组件需要通过 props 接收外部传入的数值,但调用方错误地传递了一个字符串类型的数据,这可能导致组件内部出现错误。
为了增强类型安全性,Vue 允许在定义组件的 props 时添加约束,对其类型、默认值、是否可选等进行配置。这有助于确保组件接收到正确类型的数据,并在数据不符合预期时提供明确的错误信息。
新建一个名为 demo.props.html 的测试文件。在其中编写如下核心代码:
<div id="Application"> <comp1 :count="5"></comp1> </div> <script> const { createApp, ref, computed } = Vue; const App = createApp({}); const comp1 = { props: ["count"], // 定义外部属性 count setup(props) { const thisCount = ref(0); const click = () => { thisCount.value += 1; }; const innerCount = computed(() => { return props.count + thisCount.value; }); return { thisCount, click, innerCount }; }, template: ` <button @click="click">单击</button> <div>计数: {{ innerCount }}</div> ` }; App.component("comp1", comp1); App.mount("#Application"); </script>在上述代码中,定义了一个名为 count 的外部属性,这个属性在组件内实际上用于控制组件计数的初始值。需要注意,在外部传递数值类型的数据到组件内部时,必须使用 v-bind 指令的方式进行传递,直接使用 HTML 属性设置的方式会将传递的数据作为字符串传递(而不是 JavaScript 表达式)。
例如,下面的组件的使用方式,最终页面渲染的计数结果将不是预期的:
<comp1 count="5"></comp1>
虽然 count 属性的本意是作为组件内部计数的初始值,但调用方不一定能完全理解组件内部的逻辑,调用此组件时极有可能会传递非数值类型的数据,例如:
<comp1 :count="{}"></comp1>页面渲染效果如下图所示:

图 1 组件渲染示例
可以看到,其渲染结果并不正常。在 Vue 中,我们可以对定义的 props 进行约束来显式地指定其类型。当将组件的 props 配置项配置为列表时,表示当前定义的属性没有任何约束控制,如果将其配置为对象,则可以进行更多约束设置。
修改上面代码中 props 的定义如下:
props: { count: { // 定义此属性的类型为数值类型 type: Number, // 设置此属性是否必传 required: false, // 设置默认值 default: 10 } }此时,在调用此组件时,如果设置 count 属性的值不符合要求,则控制台会有警告信息输出,例如 count 设置的值不是数值类型,则会抛出如下警告:
[Vue warn]: Invalid prop: type check failed for prop "count". Expected Number with value NaN, got Object在实际开发中,我们建议所有的 props 都采用对象的方式定义,显式地设置其类型、默认值等,这样不仅可以使组件调用时更加安全,也侧面为开发者提供了组件的参数使用文档。
如果只需要指定属性的类型,而不需要指定更加复杂的性质,可以使用如下方式定义:
props: { // 数值类型 count: Number, // 字符串类型 count2: String, // 布尔值类型 count3: Boolean, // 数组类型 count4: Array, // 对象类型 count5: Object, // 函数类型 count6: Function }
如果一个属性可能是多种类型,可以如下定义:
props:{ // 指定属性类型为字符串或数值 param:[String, Number] }
在对属性的默认值进行配置时,如果默认值的获取方式比较复杂,也可以将其定义为函数,函数执行的结果会被作为当前属性的默认值,示例代码如下:
props: { count: { default: function () { return 10 } } }
Vue 中 props 的定义支持进行自定义验证。以上述代码为例,假设组件内需要接收的 count 属性的值必须大于数值 10,则可以通过自定义验证函数实现:
props: { count: { validator: function (value) { if (typeof value !== 'number' || value <= 10) { return false; } return true; } } }当组件的 count 属性被赋值时,会自动调用验证函数进行验证,如果验证函数返回 true,则表明此赋值是有效的,如果验证函数返回 false,则控制台会输出异常信息。
props的只读性质
你可能已经发现了,对于组件内部来说,props 是只读的。也就是说,我们不能在组件的内部修改 props 属性的值,可以尝试运行如下代码:<script> const { createApp, ref, computed } = Vue; const App = createApp({}); const comp1 = { props: { count: { validator: function (value) { if (typeof value !== 'number' || value <= 10) { return false; } return true; } } }, setup(props) { // 只读解构,不可直接修改 props const { count } = props; // 需要响应式可改的值,应使用 ref const localCount = ref(count); const click = () => { localCount.value += 1; }; return { localCount, click }; }, template: ` <button @click="click">点击</button> <div>计数:{{ localCount }}</div> ` }; App.component('comp1', comp1); App.mount('#Application'); </script>当 click 函数被触发时,页面上的计数并没有改变,并且控制台会抛出 Vue 警告信息。
props 的这种只读性是Vue单向数据流特性的一种体现。对于所有的外部属性,props 都只允许父组件的数据流动到子组件中,子组件的数据则不允许流向父组件。因此,在组件内部修改 props 的值是无效的。
以计数器页面为例,如果我们定义 props 只是为了设置组件某些属性的初始值,完全可以使用计算属性来进行桥接,也可以将外部属性的初始值映射到组件的内部属性上,示例代码如下:
setup(props) { const { count } = props; const thisCount = ref(count); const click = () => { thisCount.value += 1; }; const innerCount = computed(() => { return count + thisCount.value; }); return { innerCount, thisCount, click }; }
组件数据注入
数据注入是一种便捷的组件间的数据传递方式。一般情况下,当父组件需要传递数据到子组件时,我们会使用 props,但是当组件的嵌套层级很多,子组件需要使用到多层之外的父组件的数据时,就非常麻烦了,数据需要一层一层地进行传递。新建一个名为 demo.provide.html 的测试文件,在其中编写如下核心示例代码:
<div id="Application"> <my-list :count="5"></my-list> </div> <script> const { createApp, ref } = Vue; const App = createApp({}); // 列表组件 const listCom = { props: { count: Number // 定义外部属性 count }, // 组件模板:渲染一个列表容器 template: ` <div style="border: red solid 10px;"> <my-item v-for="i in count" :key="i" :list-count="count" :index="i"> </my-item> </div> ` }; // 列表项组件 const itemCom = { props: { listCount: Number, // 列表项总数 index: Number // 当前列表项下标 }, template: ` <div style="border: blue solid 10px;"> <my-label :list-count="listCount" :index="index"></my-label> </div> ` }; // 标签组件 const labelCom = { props: { listCount: Number, index: Number }, template: `<div>{{ index }}/{{ listCount }}</div>` }; // 注册组件 App.component("my-list", listCom); App.component("my-item", itemCom); App.component("my-label", labelCom); App.mount("#Application"); </script>在上述代码中,我们创建了 3 个自定义组件,my-list 组件用来创建一个列表视图,其中每一行的元素为 my-item 组件,在 my-item 组件中又使用了 my-label 组件进行文本显示。列表中的每一行会渲染出当前的行数以及总行数。
运行上述代码,页面效果如下图所示:

图 2 自定义列表组件
运行上述代码本身没有什么问题,烦琐的地方在于 my-label 组件中需要使用到 my-list 组件中的 count 属性,通过使用 my-item 组件,数据才能顺利进行传递。随着组件嵌套层数的增多,数据的传递将越来越复杂。对于这种场景,我们可以使用数据注入的方式来跨层级进行数据传递。
所谓数据注入,是指父组件可以向其所有子组件提供数据,不论在层级结构上此子组件的层级有多深。以上面的代码为例,my-label 组件可以跳过 my-item 组件,直接使用 my-list 组件中提供的数据。
实现数据注入,需要使用 provide 与 inject 两个函数,提供数据的父组件需要设置 provide 配置项来提供数据,子组件需要设置 inject 配置项来获取数据。修改上述代码如下:
<script> const { createApp, ref, provide, inject } = Vue; const App = createApp({}); const listCom = { props: { count: Number }, setup(props) { // 这里需要提供组件内共享的数据 provide("listCount", props.count); }, template: ` <div style="border: red solid 10px;"> <my-item v-for="i in count" :key="i" :index="i"></my-item> </div> ` }; const itemCom = { props: { index: Number }, template: ` <div style="border: blue solid 10px;"> <my-label :index="index"></my-label> </div> ` }; const labelCom = { props: { index: Number }, setup() { // 此组件需要使用 provide 提供的数据,使用 inject 进行注入 const listCount = inject("listCount"); return { listCount }; }, template: ` <div>{{ index }}/{{ listCount }}</div> ` }; App.component("my-list", listCom); App.component("my-item", itemCom); App.component("my-label", labelCom); App.mount("#Application"); </script>运行代码,程序依然可以很好地运行。使用数据注入的方式传递数据时,父组件不需要了解哪些子组件要使用这些数据,同样子组件也无须关心所使用的数据来自哪里。一定程度来说,这使代码的可控性降低了。因此,在实际开发中,我们要根据场景来决定使用怎样的方式来传递数据,而不是滥用注入技术。