Vue 的 props 是组件通信的核心机制,遵循“props down, events up”原则:父组件通过 props 向下传递数据,子组件通过事件向上通信。在日常开发中,正确使用 props 能显著提高组件的可维护性和复用性。本文从实战角度系统讲解 props 的静态/动态传值、命名规范、类型验证、单向数据流限制以及三种修改 props 数据的推荐方案。
## 基础:父子组件定义
先定义两个组件 parent 和 child,以及一个 Vue 实例,在 #example 容器中挂载 parent:
- var childNode = {
- template: `
- <div>childNode</div>
- `
- };
- var parentNode = {
- template: `
- <div>
- <child></child>
- <child></child>
- </div>
- `,
- components: {
- child: childNode
- }
- };
- new Vue({
- el: "#example",
- components: {
- parent: parentNode
- }
- });
复制代码
HTML 中只需放置 <parent></parent> 即可。这种嵌套结构是组件化的基础。
## 静态 props:父组件通过特性传递固定值
子组件必须显式声明 props,才能接收父组件传入的数据。命名时,父组件模板中使用中划线写法(kebab-case),子组件 props 声明时可用小驼峰或中划线,但子组件模板中使用对应的小驼峰名称,Vue 会自动转换。
- var childNode = {
- template: `
- <div>{{ forChildMsg }}</div>
- `,
- props: ["for-child-msg"]
- };
- var parentNode = {
- template: `
- <div>
- <p>parentNode</p>
- <child for-child-msg="aaa"></child>
- <child for-child-msg="bbb"></child>
- </div>
- `,
- components: {
- child: childNode
- }
- };
复制代码
注意:模板中 for-child-msg 对应子组件 props 里的 "for-child-msg",在子组件模板中使用 {{ forChildMsg }}(小驼峰)。
## 动态 props:利用 v-bind 绑定父组件数据
动态传递时,父组件将 data 中的变量通过 v-bind(简写 : )绑定到子组件的 props 上:
- var parentNode = {
- template: `
- <div>
- <p>parentNode</p>
- <child :for-child-msg="childMsg1"></child>
- <child :for-child-msg="childMsg2"></child>
- </div>
- `,
- components: {
- child: childNode
- },
- data: function() {
- return {
- childMsg1: "Dynamic props msg for child-1",
- childMsg2: "Dynamic props msg for child-2"
- };
- }
- };
复制代码
父组件 data 中的 childMsg1 和 childMsg2 会实时传递给子组件。
## props 验证:确保数据类型正确
当组件需要更严谨的数据约束时,可以用对象语法声明 props,并指定类型、是否必传、默认值或自定义校验函数。
- Vue.component("example", {
- props: {
- // 基础类型检测,null 表示任意类型
- propA: Number,
- // 多种类型
- propB: [String, Number],
- // 必传且为 String
- propC: {
- type: String,
- required: true
- },
- // 数字有默认值
- propD: {
- type: Number,
- default: 101
- },
- // 对象/数组默认值必须用工厂函数返回
- propE: {
- type: Object,
- default: function() {
- console.log("propE default invoked.");
- return { message: "I am from propE." };
- }
- },
- // 自定义验证函数
- propF: {
- validator: function(value) {
- return value > 100;
- }
- }
- }
- });
复制代码
支持的类型包括:String, Number, Boolean, Function, Object, Array, Symbol。验证函数必须命名为 validator,自定义名称不生效。
一个典型应用:验证子组件接收的数字必须大于 100,否则 Vue 会抛出警告。
- let childNode = {
- template: "<div>{{ forChildMsg }}</div>",
- props: {
- "for-child-msg": {
- validator: function(value) {
- return value > 100;
- }
- }
- }
- };
复制代码
## 单向数据流与修改 Props 的问题
Props 是单向绑定的,父组件数据变化会自动流向子组件,但子组件不能修改 props。尝试在子组件中直接修改会触发 Vue 警告:
- [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "forChildMsg"
复制代码
场景:子组件希望将传入的 prop 当作初始值或中间数据进行加工。方案有三种:
### 方案一:用局部变量存储初始值
- let childNode = {
- template: `
- <div>
- <p>forChildMsg: {{ forChildMsg }}</p>
- <p>ownChildMsg: {{ ownChildMsg }}</p>
- </div>
- `,
- props: {
- "for-child-msg": String
- },
- data() {
- return { ownChildMsg: this.forChildMsg };
- }
- };
复制代码
缺点:ownChildMsg 只获得初始值,父组件更新后不会同步变化。
### 方案二:使用计算属性派生数据
- let childNode = {
- template: `
- <div>
- <p>forChildMsg: {{ forChildMsg }}</p>
- <p>ownChildMsg: {{ ownChildMsg }}</p>
- </div>
- `,
- props: {
- "for-child-msg": String
- },
- computed: {
- ownChildMsg() {
- return this.forChildMsg + "---ownChildMsg";
- }
- }
- };
复制代码
计算属性会随父组件数据实时更新,但它是只读的,不能通过输入等方式修改。
### 方案三:局部变量 + watch 同步更新
- let childNode = {
- template: `
- <div>
- <p>forChildMsg: {{ forChildMsg }}</p>
- <p>ownChildMsg: {{ ownChildMsg }}</p>
- </div>
- `,
- props: {
- "for-child-msg": String
- },
- data() {
- return { ownChildMsg: this.forChildMsg };
- },
- watch: {
- forChildMsg() {
- this.ownChildMsg = this.forChildMsg;
- }
- }
- };
复制代码
这是最灵活的实现:初始值来自 props,后续每当父组件更新,watch 自动将新值赋给局部变量,既保持响应又能安全修改。
## 总结
Props 是 Vue 组件通信的基石。本文覆盖了从静态传值到动态绑定、类型验证、数据流限制及安全修改的完整实践。理解这些模式,能帮助你写出更健壮、可维护的组件。 |