在组件间传递数据的操作,称为组件通信。
父组件向子组件传值
通过子组件的 props 选项接收父组件的传值。
Vue..component('my-component', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
注意:props 不要与 data 存在同名属性。
父组件设置方式如下:
<div id="app">
<!-- 传入静态内容 -->
<my-component title="示例内容1"></my-component>
<!-- 通过绑定形式传入静态内容,需确保内容为字符串 -->
<my-component :title="'示例内容1'"></my-component>
<!-- 动态绑定父组件的 title 数据 -->
<my-component :title="item.title"></my-component>
</div>
new Vue({
el: '#app',
data: {
item: {
title: '父组件中的数据'
}
}
})
Props 命名规则
建议 prop 命名使用 camelCase,父组件绑定时使用 kebab-case。
<div id="app">
<my-component my-title="示例内容1"></my-component>
<my-component :my-title="'示例内容1'"></my-component>
<my-component :my-title="item.title"></my-component>
</div>
Vue..component('my-component', {
props: ['myTitle'],
template: '<h3>{{ myTitle }}</h3>'
})
单向数据流
父子组件间的所有 prop 都是单向下行绑定的。如果子组件要处理 prop 数据,应当存储在 data 中后操作。
Vue.component('my-component', {
props: ['initialTitle'],
template: '<h3>{{ myTitle }}</h3>',
data () {
return {
myTitle: this.initialTitle
}
}
})
注意:如果 prop 为数组或对象时,子组件操作将会影响到父组件的状态。
Props 类型
Prop 可以设置类型检查,这时需要将 props 更改为一个带有验证需求的对象,并指定对应类型。
// 子组件
Vue.component('MyComponentA', {
props: {
parStr: String,
parArr: Array,
parAny: null // parAny: undefined 任意类型
},
template: `
<div>
{{ parStr }}
{{ parArr }}
{{ parAny }}
</div>
`
})
new Vue({
el: '#app',
data: {
str: '示例内容',
arr: [1, 2, 3],
any: '任意类型均可'
}
})
<div>
<my-component-a
:par-str="str"
:par-arr="arr"
:par-any="any"
></my-component-a>
</div>
prop 还可以同时指定多个类型,通过数组方式保存即可。
Vue.component('MyComponentA', {
props: {
parData: [String, Number],
},
template: `
<div>
{{ parData }}
</div>
`
})
Props 验证
当 prop 需要设置多种规则时,可以将 prop 的值设置为选项对象。
- 之前的类型检测功能通过 type 选项设置。
- required 用于设置数据为必填项。
- default 用于给可选项指定默认值,当父组件未传递数据时生效。当默认值为数组或对象时,必须为工厂函数返回的形式。
- validator 用于给传入的 prop 设置校验函数,return 值为 false 时 Vue.js 会发出警告。验证函数中无法使用实例的 data、methods 等功能。
Vue.component('MyComponentA', {
props: {
parNum: {
type: Number,
required: true
},
parData: {
type: [String, Boolean],
default: false
},
parArr: {
type: Array,
default: function () {
return [1, 2, 3]
}
},
parStr: {
type: String,
validator: function (value) {
return value.startWith('lagou')
}
}
},
template: `
<div>
{{ parNum }|}
{{ parData }}
{{ parArr }}
{{ parStr }}
</div>
`
})
非 Props 属性
当父组件给子组件设置了属性,但此属性在 props 中不存在,这时会自动绑定到子组件的根元素上。
Vue.component('MyComponentA', {
template: `<p>子组件内容</p>`
})
<div id="app">
<my-component-a
demo-attr="自定义属性"
title="示例title"
style="height: 200px"
class="colorBlue"
></my-component-a>
</div>
如果组件根元素已经存在了对应属性,则会替换组件内部的值。 class 与 style 是例外,当内外都设置时,属性会自动合并。
Vue.component('MyComponentA', {
template: `<p title="原始title" class="fl" style="width: 200px;">子组件内容</p>`
})
如果不希望继承父组件设置的属性,可以设置 inheritAttrs: false,但只适用于普通属性,class 与 style 不受影响。
Vue.component('MyComponentA', {
inheritAttrs: false,
template: `<p title="原始title" class="fl" style="width: 200px;">子组件内容</p>`
})
子组件向父组件传值
子向父传值需要通过自定义事件实现。
什么时候需要子组件向父组件传值?比如当商品为子组件,购物车为父组件时,父组件需要统计商品个数,就需要在子组件个数变化时传值给父组件。
<div id="app">
<h3>购物车</h3>
<product-item
v-for="product in products"
:title="product.title"
:key="product.id"
></product-item>
<p>总数为:{{ totalCount }}</p>
</div>
// 父组件
new Vue({
el: '#app',
data: {
products: [
{ id: 1, title: '苹果1斤' }
{ id: 2, title: '橙子2个' }
{ id: 3, title: '香蕉3根' }
]
},
totalCount: 0
})
// 子组件
Vue.component('product-item', {
props: ['title'],
template: `
<div>
<span>商品名称:{{ title }},商品个数:{{ count }}</span>
<button @click="countIns">+1</button>
</div>
`,
data () {
return { count: 0 }
},
methods: {
countIns () {
this.count++
}
}
})
子组件数据变化时,通过 $emit() 触发自定义事件。自定义事件名称建议使用 kebab-case 命名方式。
Vue.component('product-item', {
...
methods: {
countIns () {
this.$emit('count-change')
this.count++
}
}
})
父组件监听子组件的自定义事件,并设置处理程序。
<div id="app">
...
<product-item
...
@count-change="totalCount++"
></product-item>
...
</div>
自定义事件传值
子组件触发事件时可以向父组件传值。
// 子组件
Vue.component('product-item', {
props: ['title'],
template: `
<div>
<span>商品名称:{{ title }},商品个数:{{ count }}</span>
<button @click="countIns5">+5</button>
</div>
`,
...
methods: {
countIns5 () {
this.$emit('count-change', 5)
this.count += 5
}
}
})
父组件在监听事件时,通过 $event 接收子组件传递的数据。
<div id="app">
...
<product-item
...
@count-change="totalCount += $event"
></product-item>
...
</div>
除了在监听自定义事件的值中直接书写处理代码之外,还可以通过设置处理程序来处理传递的数据。
<div id="app">
...
<product-item
...
@count-change="onCountChange"
></product-item>
...
</div>
new Vue({
...
methods: {
onCountChange (productCount) {
this.totalCount += productCount
}
}
})
组件与 v-model
v-model 用于组件时,需要通过 props 与自定义事件实现。
<div id="app">
<p>输入内容为:{{ iptValue }}</p>
<com-input v-model="iptValue"></com-input>
</div>
// 子组件
var ComInput = {
props: ['value'],
template: `
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
`
}
// 父组件
new Vue({
el: '#app',
data: {
iptValue: ''
},
componets: {
ComInput
}
})
非父子组件传值
非父子组件指的是兄弟组件或完全无关的两个组件。
兄弟组件传值
兄弟组件可以通过父组件进行数据中转。
<div id="app">
<com-a
@value-change="value = $event"
></com-a>
<com-b
:value="value"
></com-b>
</div>
// 子组件 A
Vue.component('ComA', {
template: `
<div>
组件A的内容:{{ value }}
<button
@click="$emit('value-change', value)"
>发送</button>
</div>
`,
data () {
return { value: '示例内容' }
}
})
// 子组件 B
Vue.componet('ComB', {
props:['value'],
template: `
<div>
组件B接收到:{{ value }}
</div>
`
})
// 父组件
new Vue({
el:'#app',
data: {
value: ''
}
})
EventBus
当组件嵌套关系复杂时,根据组件关系传值会较为繁琐,而组件为了数据中转,data 中会存在许多与当前组件功能无关的数据,这时候就需要一个工具进行数据传递的管理。
EventBus (事件总线)就是一个独立的事件中心,用于管理不同组件间的传值操作。其通过一个新的 Vue 实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递。
// EventBus.js
var bus = new Vue()
发送数据的组件触发 bus 事件,接收的组件给 bus 注册对应事件。
Vue.component('product-item', {
template: `
<div>
<span>商品名称:苹果,商品个数:{{ count }}</span>
<button @click="countIns">+1</button>
</div>
`,
data () {
return { count: 0 }
},
methods: {
countIns () {
bus.$emit('countChange', 1)
this.count++
}
}
})
给 bus 注册对应事件通过 $on() 操作。
Vue.component('product-total', {
template: `
<p>总个数为:{{ totalCount }}</p>
`,
data () {
return { totalCount: 0 }
},
created () {
bus.$on('countChange', (productCount) => {
this.totalCount += productCount
})
}
})
最后创建根实例执行代码即可。
new Vue({
el: '#app'
})
其他通信方式
$root
$root 用于访问当前组件树根实例,设置简单的 Vue 应用时可以通过此方式进行组件传值。
<div id="app">
<p>父组件数据:{{ count }}</p>
<com-a></com-a>
</div>
// 子组件 A
var ComA = {
template: `
<div>
组件 A:{{ $root.count }}
<button @click="clickFn">+1</button>
</div>
`,
methods: {
clickFn () {
this.$root.count++
}
}
}
// 根组件
new Vue({
el: '#app',
data: {
count: 0
},
components: {
ComA
}
})
除了 $root
, Vue.js 中还提供了 $parent
与 $children
用于便捷访问父子组件。
$refs
$refs
用于获取设置了 ref 属性的 HTML 标签或子组件。给普通 HTML 标签设置 ref 属性,$refs 就可以获取 DOM 对象。
<div id="app">
<input type="text" ref="inp">
<button @click>按钮</button>
</div>
new Vue({
el: '#app',
methods: {
fn () {
this.$refs.inp.focus()
}
}
})
给子组件设置 ref 属性,渲染后可通过 $refs 获取子组件实例。
<div id="app">
<com-a ref="comA"></com-a>
</div>
var ComA = {
template: `<p>组件 A:{{ value }}</p>`,
data () {
return {
value: '这是组件A的数据'
}
}
}
new Vue({
el: '#app',
components: {
ComA
},
mounted () {
console.log(this.$refs)
this.$refs.comA.value = '修改子组件数据'
}
})