天地不仁以万物为刍狗,宇宙无义视众生如蝼蚁
——萧鼎和我
上一节列出了5个关键点,第一个路由已经解决了,接下来解决第二个问题:
组件的通信问题
一、组件的关系
组件之间的关系无非就是两种父子关系和没有父子关系。为什么我这样说呢?
按道理应该还有兄弟关系(也就是并列的组件,比如一个组件中引用了hreder和footer组件。),还有爷孙关系(比如我有七个Calabash Brothers组件,放在的HanHan组件下,而HanHan组件放在了Chairman Mao组件下)
那么不应该是父子、爷孙、兄弟关系吗?
然而并不是,因为我看了vue的文档。他的意思就是父子通信和非父子通信。
二、父子之间的通信——Prop和自定义事件
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。
prop 是父组件用来传递数据的一个自定义属性。子组件需要显式地用 props 选项声明 “prop”。
将我们的App.vue当作父组件,将test当作子组件(什么当作,本来就是)。
在App.vue中修改我们的<test>:
<!-- 传递一个say值 -->
<test say="你是猪"></test>
在Test.vue中接收,并在页面中显示:
<template>
<div>
<p>我是全英雄联盟最骚的骚猪</p>
<p>说: {{say}}</p>
<!-- 绑定say值到页面上 -->
</div>
</template>
<script>
export default {
name: 'test',
props: ['say'] //这里接收父组件穿过来的say值
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
p {
color: red;
}
</style>
然后在浏览器的显示效果如下:
综上所述可以看出,其实所谓的prop就是在<test></test>标签添加一个自定义的属性,然后在子组件中取出这个属性,用Jquery也可以做嘛(满脑子,骚,骚想法.jpg)。
上面的例子很漂亮的把父传子的通信方式展现出来了。但是子传父呢?
vue文档中使用的自定义事件。
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
我们还是用APP.vue作为父组件,Test.vue作为子组件
App.vue
...
/*增加一个位置来显示子组件传过来的值*/
<p>我儿子对我说: {{noSay}}</p>
/* 增加一个自定义的事件mychild,并给他指定触发的方法*/
<test say="你是猪" v-on:myChild="toFatherSay"></test>
...
data () {
return {
noSay: '' // 用来接收子组件穿过来的数据
}
},
methods: {
toFatherSay: function(massage) { // mychlid事件触发调用的方法
this.noSay = massage // massage就是子组件穿过来的内容
}
}
Test.vue
....
/*增加一个按钮,一点击就向父组件传值*/
<button v-on:click="toFather">点我传给父组件</button>
....
data() {
return {
massage: '我才不说呢' // 定义一个向父组件传递的值
}
},
methods: {
toFather: function() { // 按钮点击触发的方法
this.$emit('myChild',this.massage)// 使用$emit来向父组件传播
}
},
....
做完以上操作之后在浏览器上测试:
三、非父子关系之间的通信——eventBus
在veu文档上,非父子之间的通信是通过使用一个空的Vue实例作为中央事件总线。
空的Vue实例? and 中央事件总线?
空的Vue实例也就是说
var bus = new Vue(); // 的确是一个空的
中央事件总线,难道组件通信要通过全局的事件来进行?
的确是这样,vue提供了$emit和$on方法来进行参数监听(其实就是个发布订阅模式)。
创建一个空的Vue实例 Bus.js:
import Vue from 'vue'
export default new Vue();
将我们的Apart.vue和Bpart.vue当作非父子关系组件:
Apart.vue
<template>
<div>
<p>我是Apart</p>
<a v-on:click="goPage">点我切换</a>
</div>
</template>
<script>
import Bus from '../Bus' //引入创建的Bus对象
export default {
name: 'test',
methods: {
goPage: function () {
Bus.$emit('whiteSay', '克里斯,关下门') // 使用$emit方法创建一个键值对
this.$router.push('/bb')
}
},
/*将引入的Bus组件注入我们的组件对象中 import进来是不够的 还要让组件对象知道 */
components: {
Bus
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
p {
color: red;
}
div {
width: 100%;
height: 100px;
background-color: green;
}
</style>
Bpart.vue
<template>
<div>
<p>我是Bpart</p>
<p>Apart对我说: {{whiteSay}}</p>
<a v-on:click="goPage">点我切换</a>
</div>
</template>
<script>
import Bus from '../Bus' // 同样要引入Bus
export default {
name: 'test',
data () { // data用于存放组件的数据,必须是一个function,并且返回一个对象
return {
whiteSay: 'nihao'
}
},
methods: {
goPage: function () {
this.$router.push('/')
}
},
components: { // 同样要注入Bus
Bus
},
created: function() { // 在组件被创建时候将会执行此函数 相当于进入页面的自执行
Bus.$on('whiteSay', function(data) { // 使用$on方法监听white属性并执行一个回调函数
this.whiteSay = data
console.log(this.whiteSay)
});
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
p {
color: red;
}
div {
width: 100%;
height: 100px;
background-color: yellow;
}
</style>
然后在浏览器中测试一下:
有问题!!!无论怎么点击我们发现Bpart中定义的whiteSay并没有改变,并且第一次点击控制台没有打印。我在Bpart中写了这段代码:
data () {
return {
whiteSay: 'nihao'
}
},
created: function() { // 在组件被创建时候将会执行此函数 相当于进入页面的自执行
Bus.$on('whiteSay', function(data) { // 使用$on方法监听white属性并执行一个回调函数
this.whiteSay = data
console.log(this.whiteSay)
});
}
按道理在元素被创建的时候,会将监听到的值赋给whiteSay并且打印。但是我们注意到第一次点击,两个操作都没有执行,也就是说没有监听到whiteSay值的变化。而第二次之后都监听到了。这是为什么?为什么把值赋给data中定义的whiteSay之后没有网页没有更新?
带着这两个问题我去问了度娘和股哥。一下是答案:
第一个为什么: 项目中使用了vue-router,会先加載新的組件,等新的組件渲染好但是還沒掛載前,銷毀舊組件,在掛載新組件。将Apart.vue的代码修改为:
...
methods: {
goPage: function () {
this.$router.push('/bb')
}
},
/*Vue 实例销毁后调用 就是所谓的生命周期钩子*/
destroyed() {
Bus.$emit('whiteSay', '克里斯,关下门') // 使用$emit方法创建一个键值对
},
...
这样第一个问题就解决了。附上找到的答案连接:https://segmentfault.com/q/10...
第二个为什么:这个是我自己代码有问题,问了隔壁大神。说是我的作用域有问题,将Bpart.vue中的代码改为:
···
created: function() { // 在组件被创建时候将会执行此函数 相当于进入页面的自执行
var _self = this; // 将当前作用域保存在变量中,和$on()的作用域区分开来
Bus.$on('whiteSay', function(data) { // 使用$on方法监听white属性并执行一个回调函数
_self.whiteSay = data
console.log(_self.whiteSay)
});
}
···
这样所有的问题就都解决了。
四、Vuex
当我使用了上面几种方法来实现组件的通信存在着一些缺陷。比如父组件向子组件传一个值,子组件将值处理完了返回给父组件,这将同时用到prop和自定义事件。还不如直接写一个所有组件都可以访问的变量呢来得方便呢。比如:
/*这是vuex文档中的例子*/
const sourceOfTruth = {}
const vmA = new Vue({
data: sourceOfTruth
})
const vmB = new Vue({
data: sourceOfTruth
})
再比如当项目过大,组件之间的通信将变得难以管理。veux的初衷就是为何更好的管理组件的状态。一下是vuex文档对vuex的定义:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
写得好累,还好最近没事做,不会被boss骂。
接下来直接开始使用vuex。
先下载
在根目录下打开cmd:
npm install vuex -save
下载成功看到一下数据:
C:\Users\59227\Desktop\x-chat>npm install vuex --save
x-chat@1.0.0 C:\Users\59227\Desktop\x-chat
`-- vuex@2.1.1
npm WARN optional Skipping failed optional dependency /chokidar/fsevents:
npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.0.15
然后在main.js中引用,并安装到Vue上面
import Vuex from 'vuex'
Vue.use(Vuex)
前面两步将Vuex引入到了项目当中,接下来如何使用Vuex。
Vuex的核心是一个store(仓库)这个仓库的作用就是用来管理应用中的state(状态)。这里状态该怎么理解?
我个人的理解是:所有组件共享的并可以进行更改的对象。
除了state的,store还有getter、Mutations、Actions以及Modules。在vuex文档中都有非常详细的说明:http://vuex.vuejs.org/zh-cn/s...
笼统的说:
组件获取 state 用 vuex 的 getter
组件触发动作用 vuex 的 action
修改 state 用 vuex 的 mutation
知乎上看到的,说得很贴切易懂。
直接上代码,建议撸完代码,再去看一遍vuex的文档。
main.js
....
const store = new Vuex.Store({ //创建一个仓库
state: {
showDagger: true, // 定义一个状态
},
mutations: {// 定义 mutation ,更改 Vuex 的 store 中的状态的唯一方法是提交mutation
daggerCtrl (state) { // 一定要传入state,并且是第一个参数
state.showDagger = !state.showDagger // 将showDagger值取反
}
}
})
/* eslint-disable no-new */
new Vue({
el: '#app',
router, // 将router对象传给vue,这样就可以通过this.$router获取到router对象了
store, // 将store对象传给vue,这样就可以通过this.$store获取到store对象了
template: '<App/>',
components: { App }
})
然后更改App.vue:
<template>
<div id="app">
<img src="./assets/logo.png">
<p>我儿子对我说: {{noSay}}</p>
<test say="你是猪" v-on:myChild="toFatherSay"></test>
<router-view></router-view>
<!-- @click是v-on:click的简写方式 -->
<button @click="changeDagger">dagger</button> 1.添加按钮和组件
<dagger></dagger>
</div>
</template>
<script>
import Test from './components/Test' // 这里引入Test组件
import Dagger from './components/Dagger' // 引入Dagger组件 2.引入Dagger
export default {
name: 'app',
components: {
Test, // 在components中添加Test
Dagger 3.注入Dagger
},
data () {
return {
noSay: ''
}
},
methods: {
toFatherSay: function(massage) {
this.noSay = massage
},
changeDagger: function() { 4.增加按钮点击触发的事件
this.$store.commit('daggerCtrl') // 使用commit(提交)方法唤醒名为daggerCtrl的mutation
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
dagger.vue
<template>
<div class="dagger" v-if="this.$store.state.showDagger">
<h1>Dagger</h1>
</div>
</template>
<script>
</script>
<style scoped>
.dagger {
margin: 0 auto;
width: 50%;
height: 100px;
background-color: red;
}
</style>
打开浏览器 看效果:
使用vuex实现组件通信就搞定了,更多的用法请参考vuex文档。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。