首页 > 编程笔记
Vue父组件给子组件传值(超级详细)
在 Vue.js 中,可以在定义子组件中定义多个 prop 属性,用来接收父组件传过来的数据。也就是说,父组件可以通过子组件的 prop 属性,给子组件传递值。
定义一个 ViewCount 组件,显示通过 propCount 传入的值。在 vm 根实例组件中,注册并且使用 ViewCount 组件,显示单击按钮累计的单击次数,代码如下:
另外,每次父级组件发生变更时,子组件中所有的 prop 都将被刷新为最新的值。这意味着开发人员不应该在一个子组件的内部改变 prop。如果这样做了,Vue.js 则会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个prop的情形:
1) 使用 prop attribute 将一个初始值传递给子组件的本地属性,子组件直接操作本地属性。
2) 在子组件中定义计算属性,基于 prop attribute 传入的值进行计算处理。
代码如下:
为了定制 prop 的验证方式,开发人员可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组,代码如下:
实例的属性是在对象创建之前进行验证的,所以实例的属性(如 data 和 computed)在 default 和 validator 函数中不可用。
prop 支持的类型包括:String、Number、Boolean、Array、Object、Date、Function、Symbol,同时支持自定义的构造函数,能使用 instanceof 进行确认,代码如下:
显式定义的 prop 适用于向一个子组件传入信息,这也是 Vue.js 中推荐的做法,即向子组件传值的方式,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。
如下例子中的 son-component 组件的 notprop 属性,没有在 son-component 的 props 属性中定义,但是会被直接渲染到子组件的根元素(div元素)中,代码如下:
如上面的代码中,在 son-component 组件中定义了 class="subClass"name="subName",同时在组件使用的时候定义了:class="clsValue"和:name="nameValue",最后渲染的结果是 class="parentClass subClass"和name="parentName"。class 属性的值被合并了,而 name 属性的值只有一个:外面的值被替换了组件里面的 name 值。
因为继承的 attribute 只能作用到根元素上,如果需要将 attribute 继承到子组件的非根元素上,则可以使用 v-bind="$attrs" 将 attribute 绑定到子元素的非根元素上,代码如下:
定义一个 ViewCount 组件,显示通过 propCount 传入的值。在 vm 根实例组件中,注册并且使用 ViewCount 组件,显示单击按钮累计的单击次数,代码如下:
<div id="app"> <button v-on:click="clickMe">单击我</button> <!-- 使用ViewCount组件显示单击次数 --> <view-count :prop-count="count"></view-count> </div> <template id="viewCountTemplate"> <div>{{ propCount }}</div> </template> <script type="text/javascript"> const ViewCount = { props: ['propCount'], template: '#viewCountTemplate' } const vm = new Vue({ el: '#app', components: { 'view-count': ViewCount }, data: { count: 0 }, methods: { clickMe() { this.count++; } } }); </script>传值的语法如下:
<子组件名称:prop 属性名称="表达式"></子组件名称>或者:
<子组件名称 v-bind:prop 属性名称="表达式"></子组件名称>
prop的大小写
HTML 中的 attribute 名对大小写不敏感,所以浏览器会把所有大写字符解释为小写字符。这意味着当开发人员使用 DOM 中的模板时,用 camelCase(驼峰命名法)命名的 prop 名需要使用其等价的 kebab-case(短横线分隔命名法)命名,代码如下:Vue.component('sub-component', { // 在 JavaScript 中使用的是 camelCase props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' }); // 在 HTML 中使用的是 kebab-case <sub-component :post-title="hello"></sub-component>
prop的数据类型
prop 除了支持数字和 string 类型外,还支持其他类型,代码如下:props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise }这不仅为组件提供了使用参考文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。使用方式和说明的样例代码如下。
1) 传入一个数字
<!-- 即便‘42’是静态的,仍然需要v-bind来告诉Vue.js --> <!-- 这是一个JavaScript表达式而不是一个字符串. --> <sub-component v-bind:likes="42"></sub-component> <!-- 用一个变量进行动态赋值. --> <sub-component v-bind:likes="post.likes"></sub-component>
2) 传入一个布尔值
<!-- 包含该prop没有值的情况在内,都意味着true --> <sub-component is-published></sub-component> <!-- 即便'false'是静态的,仍然需要'v-bind'来告诉Vue.js --> <!-- 这是一个JavaScript表达式而不是一个字符串. --> <sub-component v-bind:is-published="false"></sub-component> <!-- 用一个变量进行动态赋值. --> <sub-component v-bind:is-published="post.isPublished"></sub-component>
3) 传入一个数组
<!-- 即便数组是静态的,我们仍然需要'v-bind'来告诉Vue.js --> <!-- 这是一个JavaScript表达式而不是一个字符串. --> <sub-component v-bind:comment-ids="[234, 266, 273]"></sub-component> <!-- 用一个变量进行动态赋值. --> <sub-component v-bind:comment-ids="post.commentIds"></sub-component>
4) 传入一个对象
<!-- 即便对象是静态的,仍然需要'v-bind'来告诉Vue.js --> <!-- 这是一个JavaScript表达式而不是一个字符串. --> <sub-component v-bind:author="{ name: 'Veronica', company: 'Meridian Dynamics' }"></sub-component> <!-- 用一个变量进行动态赋值. --> <sub-component v-bind:author="post.author"></sub-component>
5) 传入一个对象的所有property
如果开发人员要将一个对象的所有 property 都作为 prop 传入,则可以使用不带参数的 v-bind(取代 v-bind:prop-name)。将 user 对象的所有属性传递到组件中,代码如下:<template id="subUserId"> <div> <p>name: {{ userAttr.name }}</p> <p>age: {{ userAttr.age }}</p> </div> </template> <div id="app"> <!-- 传入用户对象 --> <user-component :user="user"></user-component> </div> <script> Vue.component('user-component', { props: ['userAttr'], template: '#subUserId' }); const vm = new Vue({ el: "#app", data: { user: { name: '张三', age: 12 } } }); </script>
prop单向数据流
所有的 prop 都使其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致应用的数据流向难以理解。另外,每次父级组件发生变更时,子组件中所有的 prop 都将被刷新为最新的值。这意味着开发人员不应该在一个子组件的内部改变 prop。如果这样做了,Vue.js 则会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个prop的情形:
1) 使用 prop attribute 将一个初始值传递给子组件的本地属性,子组件直接操作本地属性。
2) 在子组件中定义计算属性,基于 prop attribute 传入的值进行计算处理。
代码如下:
<!-- 定义模板 --> <template id="templateIn"> <div> <!-- 初始值方式 --> <p> <span>传入的 counter: {{ counter }}</span><br> sub Counter: <input v-model="subCounter" /><br> <span>子组件更新 counter: {{ subCounter }}</span> </p> <!-- 计算属性方式 --> <p> <span>传入的 size: {{ size }}</span><br> subSize: <input v-model="computeSize" /><br> <span>子组件更新 size: {{ computeSize }}</span> </p> </div> </template> <div id="app"> counter: <input v-model="counter" /><br> size: <input v-model="size" /> <sub-test :counter="counter" :size="size"></sub-test> </div> <script> const subVue = { props: ['counter', 'size'], // 定义prop属性接收父组件的值 data: function() { return { subCounter: this.counter // 给本地属性赋值 }; }, computed: { computeSize: { get: function() { return 'result -> ' + this.size; }, set: function(newValue) { // 不能响应到父组件,会抛出异常 this.size = newValue.slice('result -> '.length); } } }, template: '#templateIn' }; const vm = new Vue({ el: "#app", data: { counter: 12, size: 10 }, components: { 'sub-test': subVue } }); </script>
prop属性验证
开发人员还可以为组件的 prop 指定验证要求。如果有一个需求没有被满足,则 Vue.js 会在浏览器控制台中警告。这在开发一个会被别人用到的组件时尤其有帮助。为了定制 prop 的验证方式,开发人员可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组,代码如下:
<!-- 定义模板 --> <template id="templateIn"> <div> <span>{{ attr1 }}</span><br> <span>{{ attr2 }}</span><br> <span>{{ attr3 }}</span><br> <span>{{ attr4 }}</span><br> <span>{{ attr5.message }}</span><br> <span>{{ attr6 }}</span><br> </div> </template> <div id="app"> <son-component :attr1="a1" :attr2="a2" :attr3="a3" :attr4="a4" :attr5="a5" :attr6="a6"> </son-component> </div> <script> const SonComponent = { props: { attr1: Number, attr2: [String, Number], attr3: { type: String, required: true }, attr4: { type: Number, default: 10 }, attr5: { type: Object, default: function() { return { message: 'hello' }; } }, attr6: { type: String, validator: function(value) { // 这个值必须匹配下列字符串中的一个 return ["success", "warning", "danger"].indexOf(value) !== -1; } } }, template: '#templateIn' }; const vm = new Vue({ el: "#app", components: { SonComponent }, data: { a1: 1, a2: 'hello', a3: 'world', a4: 11, a5: { message: "hai" }, a6: 'success' } }); </script>当 prop 验证失败时,Vue.js 将会发出一个控制台警告。
实例的属性是在对象创建之前进行验证的,所以实例的属性(如 data 和 computed)在 default 和 validator 函数中不可用。
prop 支持的类型包括:String、Number、Boolean、Array、Object、Date、Function、Symbol,同时支持自定义的构造函数,能使用 instanceof 进行确认,代码如下:
<!-- 定义模板 --> <template id="template1"> <div> {{ personAttr.firstName }} {{ personAttr.lastName }} </div> </template> <div id="app"> <son-component :person-attr="person"></son-component> </div> <script> function Person(first, last) { this.firstName = first; this.lastName = last; } const SonComponent = { template: '#template1', props: { personAttr: { type: Person, validator: function(value) { return value instanceof Person; } } } }; const vm = new Vue({ el: "#app", components: { SonComponent }, data: { person: new Person("san", "zhang") } }); </script>
非prop的attribute
组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。显式定义的 prop 适用于向一个子组件传入信息,这也是 Vue.js 中推荐的做法,即向子组件传值的方式,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。
如下例子中的 son-component 组件的 notprop 属性,没有在 son-component 的 props 属性中定义,但是会被直接渲染到子组件的根元素(div元素)中,代码如下:
<!-- 定义模板 --> <template id="template1"> <div class="subClass" name="subName">子组件</div> </template> <div id="app"> <son-component :notprop="notPropValue" :class="clsValue" :name="nameValue"> </son-component> </div> <script> const SonComponent = { template: '#template1' }; const vm = new Vue({ el: "#app", data: { notPropValue: "hello", clsValue: "parentClass", nameValue: "parentName" }, components: { SonComponent } }); vm.$data.notPropValue = "hai"; </script>渲染代码如下:
<div notprop="hai" class="parentclass subclass" name="parentName">子组件</div>div 中的 notprop="hai" 是从 son-component:notprop="notPropValue"/son-component 传递过去的。
1) 替换/合并已有的attribute
如果在子组件中也定义了非 prop 的 attribute,同时在使用组件的时候也定义了该 attribute,这时候最后的值,存在替换/合并问题。class 和 style 的值会被合并,其他属性值会被替换。如上面的代码中,在 son-component 组件中定义了 class="subClass"name="subName",同时在组件使用的时候定义了:class="clsValue"和:name="nameValue",最后渲染的结果是 class="parentClass subClass"和name="parentName"。class 属性的值被合并了,而 name 属性的值只有一个:外面的值被替换了组件里面的 name 值。
2) 禁用Attribute继承
如果开发人员不希望组件的根元素继承 attribute,则可以在组件的选项中设置 inheritAttrs:false因为继承的 attribute 只能作用到根元素上,如果需要将 attribute 继承到子组件的非根元素上,则可以使用 v-bind="$attrs" 将 attribute 绑定到子元素的非根元素上,代码如下:
<div id="app"> <son-component test="Value" required placeholder="请输入姓名"></son-component> </div> <script> Vue.component('SonComponent', { inheritAttrs: false, template: '<div><input type="text" v-bind="$attrs"/></div>' }); const vm = new Vue({ el: '#app' }); </script>
注意,class 和 style 属性不在作用范围。