Props 是父组件向子组件传递数据的标准方式。它让组件可配置、可复用,是组件化开发的基础。
理解 Props 的各种用法和注意事项,能帮你设计出更好的组件接口。
最简单的 props 定义方式:
Vue.component('user-card', {
props: ['name', 'age', 'email'],
template: `
<div class="user-card">
<h3>{{ name }}</h3>
<p>年龄:{{ age }}</p>
<p>邮箱:{{ email }}</p>
</div>
`
})
<user-card
name="张三"
age="25"
email="zhangsan@example.com"
></user-card>
推荐使用对象语法,可以指定类型:
Vue.component('user-card', {
props: {
name: String,
age: Number,
email: String
},
template: `...`
})
直接传递字符串:
<user-card name="张三"></user-card>
使用 v-bind 传递动态数据:
<user-card :name="userName"></user-card>
<user-card :user="currentUser"></user-card>
<!-- 静态传递是字符串 -->
<user-card age="25"></user-card> <!-- "25" 字符串 -->
<!-- 使用 v-bind 传递数字 -->
<user-card :age="25"></user-card> <!-- 25 数字 -->
<!-- 传递 true -->
<my-component :active="true"></my-component>
<!-- 简写:包含 props 默认为 true -->
<my-component active></my-component>
<!-- 传递 false -->
<my-component :active="false"></my-component>
<user-list :users="['张三', '李四']"></user-list>
<user-card :user="{ name: '张三', age: 25 }"></user-card>
<user-card v-bind="user"></user-card>
<!-- 等同于 -->
<user-card
:name="user.name"
:age="user.age"
:email="user.email"
></user-card>
指定 prop 的类型:
props: {
name: String,
age: Number,
active: Boolean,
tags: Array,
user: Object,
callback: Function,
promise: Promise
}
允许 prop 是多种类型之一:
props: {
value: [String, Number],
visible: [Boolean, Number]
}
props: {
name: {
type: String,
required: true
}
}
props: {
type: {
type: String,
default: 'primary'
},
size: {
type: Number,
default: 14
}
}
对象和数组的默认值必须使用工厂函数:
props: {
user: {
type: Object,
default: function() {
return { name: '匿名' }
}
},
tags: {
type: Array,
default: function() {
return []
}
}
}
props: {
status: {
validator: function(value) {
return ['active', 'inactive', 'pending'].indexOf(value) !== -1
}
},
age: {
validator: function(value) {
return value >= 0 && value <= 150
}
}
}
Vue.component('user-card', {
props: {
name: {
type: String,
required: true
},
age: {
type: Number,
default: 18,
validator: function(value) {
return value >= 0 && value <= 150
}
},
email: {
type: String,
default: ''
},
role: {
type: String,
default: 'user',
validator: function(value) {
return ['admin', 'user', 'guest'].indexOf(value) !== -1
}
},
tags: {
type: Array,
default: function() {
return []
}
}
},
template: `
<div class="user-card">
<h3>{{ name }}</h3>
<p>年龄:{{ age }}</p>
<p>邮箱:{{ email }}</p>
<p>角色:{{ role }}</p>
<p>标签:{{ tags.join(', ') }}</p>
</div>
`
})
Props 是单向绑定的:父组件数据变化会传递给子组件,但子组件不应该修改 props。
不要修改 Props
// ❌ 错误:直接修改 props
props: ['message'],
methods: {
update() {
this.message = 'new value' // Vue 会警告
}
}
1. 使用本地数据
props: ['initialCount'],
data() {
return {
count: this.initialCount
}
}
2. 使用计算属性
props: ['size'],
computed: {
normalizedSize() {
return this.size.trim().toLowerCase()
}
}
3. 使用事件通知父组件
props: ['value'],
methods: {
updateValue(newValue) {
this.$emit('input', newValue)
}
}
HTML 不区分大小写,所以 props 在模板中要使用短横线命名:
// JavaScript 中使用驼峰命名
props: {
userName: String,
isActive: Boolean
}
<!-- HTML 中使用短横线命名 -->
<my-component user-name="张三" is-active></my-component>
组件可以接收未在 props 中定义的属性,这些属性会自动添加到组件根元素上。
<my-component class="custom-class" style="color: red" data-id="123"></my-component>
class、style、data-id 会自动添加到组件根元素。
Vue.component('my-input', {
template: '<input type="text" class="form-control">'
})
<!-- class 会合并 -->
<my-input class="custom-input"></my-input>
<!-- 结果:<input type="text" class="form-control custom-input"> -->
<!-- type 会替换 -->
<my-input type="password"></my-input>
<!-- 结果:<input type="password" class="form-control"> -->
Vue.component('my-component', {
inheritAttrs: false,
template: `
<div>
<input v-bind="$attrs">
</div>
`
})
Vue.component('app-button', {
props: {
type: {
type: String,
default: 'primary',
validator: function(value) {
return ['primary', 'secondary', 'danger', 'success'].indexOf(value) !== -1
}
},
size: {
type: String,
default: 'medium',
validator: function(value) {
return ['small', 'medium', 'large'].indexOf(value) !== -1
}
},
disabled: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
}
},
template: `
<button
:class="['btn', 'btn-' + type, 'btn-' + size]"
:disabled="disabled || loading"
>
<span v-if="loading" class="spinner"></span>
<slot></slot>
</button>
`
})
<app-button type="primary" size="large">提交</app-button>
<app-button type="danger" :loading="isLoading">删除</app-button>
<app-button type="secondary" disabled>禁用</app-button>
Vue.component('app-input', {
props: {
value: [String, Number],
type: {
type: String,
default: 'text'
},
placeholder: String,
disabled: Boolean,
readonly: Boolean,
maxlength: Number,
label: String,
error: String
},
template: `
<div class="form-group">
<label v-if="label">{{ label }}</label>
<input
:type="type"
:value="value"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:maxlength="maxlength"
@input="$emit('input', $event.target.value)"
@change="$emit('change', $event)"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
>
<span v-if="error" class="error">{{ error }}</span>
</div>
`
})
Vue.component('data-list', {
props: {
items: {
type: Array,
required: true
},
loading: {
type: Boolean,
default: false
},
emptyText: {
type: String,
default: '暂无数据'
},
itemKey: {
type: String,
default: 'id'
}
},
template: `
<div class="data-list">
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="items.length === 0" class="empty">{{ emptyText }}</div>
<ul v-else class="list">
<li v-for="item in items" :key="item[itemKey]">
<slot :item="item">{{ item }}</slot>
</li>
</ul>
</div>
`
})
Props 验证时机
Props 验证在组件实例创建之前进行,所以 default 和 validator 函数中不能访问组件实例(this)。
// ❌ 错误:validator 中访问 this
props: {
value: {
validator(val) {
return this.someCondition // this 是 undefined
}
}
}
// ✅ 正确:使用纯函数
props: {
value: {
validator(val) {
return val >= 0 // 不依赖 this
}
}
}
引用类型 Props
对象和数组是引用类型,子组件修改会影响父组件:
props: ['user'],
methods: {
changeName() {
this.user.name = '新名字' // 会影响父组件
}
}
虽然 Vue 不会警告,但这违反了单向数据流原则。应该避免直接修改引用类型的属性。
Props 要点: