本文详解Vue中props的用法,涵盖静态与动态数据传递、命名规范、类型验证及单向数据流机制。重点解析如何避免直接修改props引发的警告,并提供通过局部变量、计算属性及watch监听三种方式安全处理父组件数据更新的方案。
Vue props 基础与父子组件通信
Vue props用法详解
在 Vue 组件开发中,props 是组件接收外部数据的关键选项之一。父子组件之间的交互关系可以概括为:
props down, events up
父组件通过 props 向下传递数据给子组件,而子组件则通过 events 事件向父组件发送消息。
父子级组件
例如,我们需要创建 parent 和 child 两个组件。为了确保每个组件在相对隔离的环境中运行,从而提高代码的可维护性,这种设计至关重要。
首先,我们定义父子两个组件以及一个 Vue 实例:
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
}
});
<div id="example">
<parent></parent>
</div>
在此示例中,childNode 组件的 template 定义为一个 div,其内容直接显示为字符串 "childNode"。
而在 parentNode 的 template 中,定义了一个 class 为 parent 的 div,其中包含了两个 child 组件实例。
若希望更系统地深入学习 Vue.js,建议参考《Vue.js实战》PDF高质量扫描版这一经典著作。
静态 Props 与命名规范
静态 props
组件实例的作用域是独立的,这意味着在子组件的模板中不应直接引用父组件的数据。若要让子组件使用父组件的数据,必须通过子组件的 props 选项进行声明。
父组件向子组件传递数据分为动态和静态两种方式,此处先介绍静态传递方式。
子组件必须显式地使用 props 选项来声明它期望接收的数据。
修改前例代码,为 childNode 添加 props 选项,并指定需要接收的 forChildMsg 数据;
随后,在父组件的模板中,通过添加特性的方式来传递数据。
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
}
};
命名规范
对于在 props 中声明的属性,在父组件的 template 模板中,属性名必须使用中划线(kebab-case)写法;
子组件在声明 props 属性时,既可以使用小驼峰(camelCase)也可以使用中划线写法;但在子组件模板中使用从父组件传来的变量时,必须使用对应的小驼峰写法。无需担心,Vue 能够正确识别小驼峰和下划线命名法混用的变量,例如这里的forChildMsg和for-child-msg被视为同一值。
动态 Props 与数据绑定
动态 props
在模板中,若要动态地将父组件的数据绑定到子组件模板的 props 上,与绑定 HTML 标签特性类似,需使用 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 的 return 数据中,childMsg1 和 childMsg2 将被传入子组件中,
Props 验证机制
props 验证
对传入的 props 参数进行数据规格验证,若不符合规格,Vue 会发出警告信息。
可判断的所有类型(即 type 值)包括:String, Number, Boolean, Function, Object, Array, Symbol。
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: {
isValid: function(value) {
return value > 100;
}
}
}
});
let childNode = {
template: "<div>{{forChildMsg}}</div>",
props: {
"for-child-msg": Number
}
};
let parentNode = {
template: `
<div class="parent">
<child :for-child-msg="msg"></child>
</div>
`,
components: {
child: childNode
},
data() {
return {
// 当这里是字符串 "123456"时会报错
msg: 123456
};
}
};
此外,还可以在 props 定义的数据中加入自定义验证函数,当该函数返回 false 时,系统将输出警告。
例如,我们将前例中的 childNode 的 for-child-msg 修改为一个对象,并包含一个名为 validator 的函数。注意,该函数名必须规定为 validator,自定义其他函数名不会生效。
let childNode = {
template: "<div>{{forChildMsg}}</div>",
props: {
"for-child-msg": {
validator: function(value) {
return value > 100;
}
}
}
};
在此处,我们为 for-child-msg 变量设置了 validator 函数,并要求传入的值必须大于 100,否则将触发警告。
单向数据流与修改策略
单向数据流
props 是单向绑定的:当父组件的属性发生变化时,数据将传导给子组件,但不会反向流动。这是为了防止子组件意外修改父组件的状态。
因此,不应在子组件中直接修改 props 中的值,否则 Vue 会发出警告。
let childNode = {
template: `
<div class="child">
<div>
<span>子组件数据</span>
<input v-model="forChildMsg"/>
</div>
<p>{{forChildMsg}}</p>
</div>`,
props: {
"for-child-msg": String
}
};
let parentNode = {
template: `
<div class="parent">
<div>
<span>父组件数据</span>
<input v-model="msg"/>
</div>
<p>{{msg}}</p>
<child :for-child-msg="msg"></child>
</div>
`,
components: {
child: childNode
},
data() {
return {
msg: "default string."
};
}
};
在此示例中,父组件和子组件各有一个输入框,分别显示父组件数据和子组件数据。当我们在父组件的输入框中输入新数据时,子组件的数据也会同步修改;这体现了 props 向子组件传递数据的功能。而当我们尝试修改子组件的输入框时,浏览器控制台则会报错警告。
[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"
修改 props 数据
通常有两种原因需要处理 props 的修改:
- prop 作为初始值传入后,子组件想把它当做局部数据来使用。
- prop 作为初始值传入后,由子组件处理成其他数据后输出。
应对办法是:
定义一个局部变量,并用 prop 的值初始化它。
然而,由于定义的 ownChildMsg 只能接受 forChildMsg 的初始值,当父组件传递的值发生变化时,ownChildMsg 无法收到更新。
let childNode = {
template: `
<div class="child">
<div>
<span>子组件数据</span>
<input v-model="forChildMsg"/>
</div>
<p>{{forChildMsg}}</p>
<p>ownChildMsg : {{ownChildMsg}}</p>
</div>`,
props: {
"for-child-msg": String
},
data() {
return { ownChildMsg: this.forChildMsg };
}
};
此处我们添加了一个 <p> 标签用于查看 ownChildMsg 数据是否变化,结果发现只有默认值传递给了 ownChildMsg,父组件的改变只会更新 forChildMsg,而不会修改 ownChildMsg。
定义一个计算属性,处理 prop 的值并返回结果。
由于是计算属性,因此只能显示值,不能设置值。这里我们设定一旦从父组件修改了 forChildMsg 数据,就将 forChildMsg 加上字符串 "---ownChildMsg",然后显示在屏幕上。此时,每当父组件修改了新数据,ownChildMsg 数据都会随之更新。
let childNode = {
template: `
<div class="child">
<div>
<span>子组件数据</span>
<input v-model="forChildMsg"/>
</div>
<p>{{forChildMsg}}</p>
<p>ownChildMsg : {{ownChildMsg}}</p>
</div>`,
props: {
"for-child-msg": String
},
computed: {
ownChildMsg() {
return this.forChildMsg + "---ownChildMsg";
}
}
};
更加稳妥的方式是使用变量存储 prop 的初始值,并利用 watch 来观察 prop 值的变化。当发生变化时,更新该变量的值。
let childNode = {
template: `
<div class="child">
<div>
<span>子组件数据</span>
<input v-model="forChildMsg"/>
</div>
<p>{{forChildMsg}}</p>
<p>ownChildMsg : {{ownChildMsg}}</p>
</div>`,
props: {
"for-child-msg": String
},
data() {
return {
ownChildMsg: this.forChildMsg
};
},
watch: {
forChildMsg() {
this.ownChildMsg = this.forChildMsg;
}
}
};
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。