Vue-组件通信
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。同时父组件也无法直接引用子组件的数据。
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。先来张直观的图。
Pass Props
首先子组件要显式地用proprs选项声明它预期的数据:
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以在模板中使用
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
然后我们在父组件里就可以传入子组件想要的这个props数据
静态传入
<child message="hello!"></child>
动态绑定
<div>
<inout v-model="parentMsg">
<br>
<child :message="parentMsg">
</div>
如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind。例如,已知一个 todo 对象:
todo: {
text: 'Learn Vue',
isComplete: false
}
然后
<todo-item v-bind="todo"></todo-item>
等价于
<todo-item
v-bind:text="todo.text"
v-bind:is-complete="todo.isComplete"
></todo-item>
初学者常犯的一个错误是使用字面量语法传递数值:
Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解
每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop 。如果你这么做了,Vue 会在控制台给出警告。
但是有时候我们确实很容易忍不住想在子组件去修改 prop 中数据
- Prop 作为初始值传入后,子组件想把它当作局部数据来用;
- Prop 作为原始数据传入,由子组件处理成其它数据输出。
对这两种情况,正确的应对方式是:
定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
定义一个计算属性,处理 prop 的值并返回:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
非 Prop 特性
所谓非 prop 特性,就是指它可以直接传入组件,而不需要定义相应的 prop。
有时候你也可以不在子组件声明props,而是不管他有什么props,直接从父组件传入一个特性,这个特性会被自动添加到子组件的根元素上。
我自己遇到过的情况,比如在使用第三方ui框架muse-ui的时候,他给的button控件<mu-button></mu-buttom>
有一个默认的样式,不过我不太喜欢他的样式所以想添加自己的样式,ui框架的设计者在有的组件上会提供诸如’activeClass’之类的props我们可以传递数据进去,但是有些控件他没有提供,我们就可以直接为他添加非prop特性(直接在自定义组件上添加class特性<mu-button class="mybtnstyle"></mu-buttom>
)来在框架控件的基础上自定义样式
当对待class和style,来自父组件添加的属性,会和组件自身模板的class属性进行merge而其他多数则会覆盖组件本身设定的值
Emit Event
父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件
一个例子
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
子组件唯一与父组件的接口就是this.$emit(‘increment’) 除此以外组件是完全独立的。因为父组件有可能会监听这个事件,所以才暴露这个接口,当子组件调用了该函数的时候通知父组件。
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符 .native。例如:
<my-component v-on:click.native="doTheThing"></my-component>
所谓原生事件应该是相对于自定义事件而言,像刚刚我们的”increment”就很明显是一个自定义事件
这个有点没懂,是因为往子组件中传入的每一个v-on:后面的值都会被当作是子组件中的自定义事件(不加native的话,会被当作我们自定义了一个名为click的事件?),需要在子组件this.$emit(‘click’)才能触发,原本的点击该元素这个操作不会触发?因此想要监听原生事件反而需要加上.native修饰符
还有一个.sync修饰符查看vue官方文档
v-model的原理
<input v-model="something">
不过是以下示例的语法糖:
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
所以在组件中使用v-model,它相当于下面的简写:
<custom-input
v-bind:value="something"
v-on:input="something = arguments[0]">
</custom-input>
vue2.2新增以下写法
<my-checkbox v-model="foo" value="some value"></my-checkbox>
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
// 如果不做配置的话,prop默认是value,event默认是input
},
props: {
checked: Boolean,
//注意你仍然需要显式声明 checked 这个 prop。
value: String
// 这样就允许拿 `value` 这个 prop 做其它事了
},
// ...
})
非父子组件的通信
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:
var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
})
再复杂的情况请考虑vuex