Vue组件通信
写在前面
vue给我的感觉就是各个组件拼装起来,通过view层来表现。有时候,某些组件的一些行为需要依赖另一个组件的传值。打个比方,我需要在上传文件的组件完成上传之后渲染另一个页面,同时我需要让上传文件的组件对用户不可见。那么,在新渲染的页面里,我怎么知道上传了什么数据,是否上传了,是因为完成上传才渲染的还是直接跳过了上传环节就进行渲染了,这里就需要进行组件之间互相传值。组件传值有非常多种实现方式,不同的实现方式产生的结果也可能不同。这里就记录一下自己写项目遇到的一些问题和心得。
软工三经历
上边的比方其实就是昨天下午写软工三大作业遇到的问题,我们软工三做的是知识图谱的编辑器,前端vue组件重要的也就是上传组件、编辑器组件。我需要实现在用户点击上传文件并且成功上传之后隐藏上传组件显示编辑器组件,并且完成对上传的文件的数据进行读取进行图谱的生成。同时,给用户提供不上传文件的选项,如果选择创建新的图谱,那么也是隐藏上传组件并显示编辑器组件,只不过是空的一张图。这个过程就涉及到了组件传值,编辑器需要知道上传组件是否上传了文件,上传组件是否选择的是创建新的图谱,上传文件是否成功。下面展示一下前端的目录结构。
从图中可以看出编辑器组件和上传组件是兄弟组件,二者其实互不相关联。显示都交给了view层来进行。而KGBuilder.vue
正是对这两个组件进行展示的文件,下面放一下KGBuilder.vue
的内容(css部分省略)。
<template>
<div>
<div id="kg_container">
<KGBuilder class="kgBuilder" pid="kg_container" v-if="showKGBuilder" :wantNew="getNew"
:hasUploaded="upLoaded" :fileList="uploadFileList"></KGBuilder>
<Uploader class="uploader" v-show="showUploader"></Uploader>
</div>
</div>
</template>
<script>
import KGBuilder from "@/components/KGBuilder";
import Uploader from "@/components/Uploader";
import {Loading} from 'element-ui';
export default {
name: "KGEditor",
components: {
Uploader,
KGBuilder
},
data() {
return {
showKGBuilder: false,
showUploader: true,
getNew: false,
upLoaded: false,
uploadFileList: []
}
},
mounted() {
let loadingInstance = Loading.service({fullscreen: true});
setTimeout(() => {
loadingInstance.close();
window.Event.$on('getNewGraph', val => {
if (val) {
// console.log(val)
this.getNew = val
this.showKGBuilder = true
this.showUploader = false
}
})
window.Event.$on('UploadFile', val => {
this.upLoaded = val
this.showKGBuilder = true
this.showUploader = false
})
window.Event.$on('transferFileArray', val => {
this.uploadFileList = val
})
}, 1000)
}
}
</script>
可以看到这里对上传组件有v-show
属性来保证是否展示,对于编辑器组件则是使用v-if
来保证是否渲染。
从代码来看,这里用了两种组件传值的方法。分别为子向父传值与父向子传值,其中,window.Event.$on
就属于子向父传值,用于监听上传组件传来的值。下面就说子向父传值的常用方法。
子组件向父组件传值
最常用的便是$emit
方法。还是用本次软工三迭代一的代码做示例。从上边view层的代码可以看出,我使用window.Event.$on
监听了三个事件,分别为getNewGraph
、uploadFile
、transferFileArray
。这三个事件都是我从子组件Uploader
内传来的。下面贴一下Uploader
的代码(css部分省略)。
<template>
<div class="Uploader">
<el-upload
class="uploadFile"
drag
action="#"
:limit="1"
:on-change="jumpToEditor"
accept="application/json"
:file-list="fileList">
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传Json文件</div>
</el-upload>
<div class="orText">或者您可以</div>
<el-button class="newGraphButton" type="info" round @click="getNewGraph">创建新的图谱</el-button>
</div>
</template>
<script>
import Vue from 'vue'
window.Event = new Vue()
export default {
name: "Uploader",
data() {
return {
fileList: [],
getUpload: false,
getGraphNew: false,
}
},
provide() {
},
beforeDestroy() {
},
methods: {
jumpToEditor(file, fileList) {
// this.$router.push({name: '/Kojima-Coin/KGEditor'})
this.fileList = fileList
if (!this.getUpload) {
this.getUpload = !this.getUpload
}
window.Event.$emit('UploadFile', this.getUpload)
window.Event.$emit('transferFileArray', this.fileList)
},
getNewGraph() {
this.getGraphNew = true
window.Event.$emit('getNewGraph', this.getGraphNew)
},
}
}
</script>
看到代码之后,可能就会比较明朗了。Uploader
中使用$emit
分别传了三个事件的值,而父组件只需要用$on
对相应的事件进行监听便可得到事件相应的传值。注意,代码中有这么两行
import Vue from 'vue'
window.Event = new Vue()
这是新建一个vue对象当作中转站。专门用于监听与发送。
碎碎念
这个传值的方法,是我之前设置全屏隐藏header
组件用的传值方法,然而当时用着并没有父组件与子组件的概念,实现的稀里糊涂的。因此昨天下午想要实现KGBuilder
与Uploader
之间的传值的时候,首先想到的便是这个方法,但是,无论如何尝试都没有成功,本来我以为是vue生命周期的问题,为此还特地去了解了这方面的问题。但是很遗憾并不是,一番尝试之后这个方法并没有行得通,遂决定另辟蹊径,换一种传值方式。然后便是接下来要说的方法。
父组件向子组件传值
因为我尝试使用$emit
与on
在两个组件之间直接通信失败,我便打算绕个路。即子A传值给父亲,父亲将子A的传值传给子B。使用父亲组件当作中转站。这里使用父组件向子组件传值的方法便是prop
。为了省的往上翻,我先再贴一下父组件的代码的<Template>
<template>
<div>
<div id="kg_container">
<KGBuilder class="kgBuilder" pid="kg_container" v-if="showKGBuilder" :wantNew="getNew"
:hasUploaded="upLoaded" :fileList="uploadFileList"></KGBuilder>
<Uploader class="uploader" v-show="showUploader"></Uploader>
</div>
</div>
</template>
有没有发现,在我打算传值的子组件多了很多属性值:wantNew="getNew" :hasUploaded="upLoaded" :fileList="uploadFileList"
这便是给子组件传值的一种方式,引号内的是父组件的变量,冒号后的是子组件内的值。下面贴一下父组件的data
和子组件的prop
父组件:
export default {
name: "KGEditor",
components: {
Uploader,
KGBuilder
},
data() {
return {
showKGBuilder: false,
showUploader: true,
getNew: false,
upLoaded: false,
uploadFileList: []
}
}
}
子组件:
export default {
name: "KGBuilder",
props: {
pid: String,
wantNew: Boolean,
hasUploaded: Boolean,
fileList: Array
}
}
需要注意的是,在子组件的prop
里,需要指定父组件传进来的值的类型。一共有十一种类型可以定义,分别是String
、Number
、Boolean
、Array
、Object
、Date
、Symbol
、Fuction
、Promise
。
其他通信方式的一些补充
$attr
与$listener
$attr
类型:
{ [key: string]: string }
只读
详细:
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (
class
和style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和style
除外),并且可以通过v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。
$listener
类型:
{ [key: string]: Function | Array<Function> }
只读
详细:
包含了父作用域中的 (不含
.native
修饰器的)v-on
事件监听器。它可以通过v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
$attrs
主要是收集父组件的属性,而$listeners
就是相当于eventListeners
,那就是收集父组件的事件。只有没有在子组件中的props
注册的父组件属性,才能被$attrs
监听到,而没有.native
修饰符的事件,才能被$listeners
所监听到。
provide
与inject
类型:
- provide:
Object | () => Object
- inject:
Array<string> | { [key: string]: string | Symbol | Object }
- provide:
详细:
provide
和inject
主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。
在父组件中注册了provide
之后,在所有的子组件,子子组件,子子孙孙,都可以注册inject
拿到父组件中的provide
中的东西。
Vuex
vuex
可以理解为整个Vue程序中的全局变量,但他和以前概念中的全局变量又有所不同,他也是响应式的,而且,你不能直接修改vuex中的变量,只能通过显式地commit=>mutation
来进行数据的改变。每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的**状态 (state)**。
五大模块
- State => state里面存放的是变量,如果你要注册全局变量,写这里
- Getter => getters相当于是state的计算属性,如果你需要将变量的值进行计算,然后输出,写这里
- Mutation => 修改store中的变量的方法,如果你要改变变量的值,就写这里
- Action => actions提交的是mutations,相当于就是改变变量的方法的重写,但是,actions是可以进行异步操作的
- Module => 将整个Vuex模块化,主要的特点就是namespaced,所有的访问都要经过命名空间
具体的使用方法这边就不再细说,网上的教程还蛮多的。这边放上官网https://vuex.vuejs.org/zh/