(本篇以Vue2为主,Vue3在写啦)
开始
(引用一张举例图,全文依据下图关系进行举例说明)
- A组件 == < aaa > < / aaa>
1.props / $emit
//父向子传值
<template 父>
<div>
<aaa :msg="传参"></aaa>
</div>
</template>
import aaa from './aaa.vue'
<template-子>
<div>
{{ msg }}
</div>
</template>
---------------------------------
//子向父组件传值
<template 父>
<div>
<aaa @send="send"></aaa>
</div>
</template>
send(val){
val为接收的参
}
<template 子>
<div>
<div @click="send">改变字符串</div>
</div>
</template>
change(){
this.$emit('send', '我是参数')
}
prop 只可以从上一级组件传递到下一级组件(父子组件),
即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。
- 第一,不应该在一个子组件内部改变 prop,这样会破坏单向的数据绑定,导致数据流难以理解。如果有这样的需要,可以通过 data 属性接收或使用 computed 属性进行转换。
- 第二,如果 props 传递的是引用类型(对象或者数组),在子组件中改变这个对象或数组,父组件的状态会也会做相应的更新,利用这一点就能够实现父子组件数据的“双向绑定”,虽然这样实现能够节省代码,但会牺牲数据流向的简洁性,令人难以理解,最好不要这样去做。
想要实现父子组件的数据“双向绑定”,可以使用 v-model 或 .sync。
$ emit 向父组件传数据,父组件在子组件通过v-on监听函数并接收参数,vue框架就在子组件监听了你v-on="fn"的fn事件函数,在子组件使用$emit就能触发了
2.v-model 指令
<template 父>
<aaa v-model="data"></aaa>
</template>
// <!-- v-model等价于以下写法 -->
// <!-- <aaa :value="data" @input='val=>data=val'> </aaa> -->
<template 子>
{{value}}
<input type="text" @click="send">
</template>
props: {
value:{ // 这里的值的名字一定要叫value 因为v-modle双向绑定的就是value属性
type:Number,
required:true,
}
},
send(){
// $emit触发input事件修改接收的值 父组件中跟着变化
this.$emit('input',this.value+1)
}
v-model 是用来在表单控件或者组件上创建双向绑定的,他的本质是 v-bind 和 v-on 的语法糖,在一个组件上使用 v-model,默认会为组件绑定名为 value 的 prop 和名为 input 的事件。
3. ( .sync ) 修饰符
<aaa v-bind:title="doc.title" v-on:update:title="doc.title = $event" />
//上面用.sync就可以写成下面
<aaa v-bind:title.sync="doc.title" />
- .sync 修饰符在 vue 1.x 的版本中就已经提供,1.x 版本中,当子组件改变了一个带有 .sync 的 prop 的值时,会将这个值同步到父组件中的值。这样使用起来十分方便,但问题也十分明显,这样破坏了单向数据流,当应用复杂时,debug 的成本会非常高。
- 在vue 2.0中移除了 .sync。但是在实际的应用中,.sync 是有它的应用场景的,所以在 vue 2.3 版本中,又迎来了全新的 .sync。新的 .sync 修饰符所实现的已经不再是真正的双向绑定,它的本质和 v-model 类似,只是一种缩写。
- .sync 从功能上看和 v-model 十分相似,都是为了实现数据的“双向绑定”,本质上,也都不是真正的双向绑定,而是语法糖。相比较之下,.sync 更加灵活,它可以给多个 prop 使用,而 v-model 在一个组件中只能有一个。从语义上来看,v-model 绑定的值是指这个组件的绑定值,比如 input 组件,select 组件,日期时间选择组件,颜色选择器组件,这些组件所绑定的值使用 v-model 比较合适。其他情况,没有这种语义,个人认为使用 .sync 更好。
4.ref
<template 父>
<aaa ref="aaaID"></aaa>
</template>
send(){
const comA = this.$refs.aaaID;
console.log(comA.name); // Vue.js
comA.send(); // aaa
}
<template 子>
<input>
</template>
data () {
return {
name: 'Vue.js'
}
},
send(){
console.log('aaa');
}
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据
- $refs 是作为渲染结果被创建的,所以在初始渲染的时候它还不存在,此时无法无法访问。
- $refs 不是响应式的,只能拿到获取它的那一刻子组件实例的状态,所以要避免在模板和计算属性中使用它。
5.$parent 和 $children
<template 父>
<div>
<div>{{msg}}</div>
<aaa></aaa>
<button @click="click">点击改变子组件值</button>
</div>
</template>
data() {
return {
msg: '父的参'
}
},
click() {
// 获取到子组件A
this.$children[0].messageA = 'this is new value'
}
<template 子>
<div >
<span>{{messageA}}</span>
<p>获取父组件的值为: {{parentVal}}</p>
</div>
</template>
data() {
return {
messageA: 'this is old'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
- 要注意边界情况,如在#app上拿$ parent得到的是new Vue()的实例,在这实例上再拿$ parent得到的是undefined,而在最底层的子组件拿$ children是个空数组。
- 也要注意得到parent和parent和parent和children的值不一样,$ children 的值是数组,而$ parent是个对象,props, $ emit 、$parent $children两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍,二者皆不能用于非父子组件之间的通信。
- 要注意边界情况,如在#app上拿$ parent得到的是new Vue()的实例,在这实例上再拿$ parent得到的是undefined,而在最底层的子组件拿$ children是个空数组。也要注意得到parent和parent和parent和children的值不一样,$ children 的值是数组,而$parent是个对象
- props $emit 、 $parent $children两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍,二者皆不能用于非父子组件之间的通信。
6.$attrs 和 $listeners ( 非父子组件通信 )
- 当要和一个嵌套很深的组件进行通信时,如果使用 prop 和 events 就会显的十分繁琐,中间的组件只起到了一个中转站的作用,像下面这样:
<!--父组件-->
<aaa :message="message">我是父组件</aaa>
<!--子组件-->
<bbb :message="message">我是子组件</bbb>
<!--孙子组件-->
<ccc :message="message">我是孙子组件</ccc>
----------------
//父和孙组件通信
<template 父>
<aaa:name="name" :message="message" @send="sayHello"></aaa>
</template>
data() {
return {
name: '通信',
message: 'Hi',
}
},
methods: {
send(mes) {
console.log('mes', mes) // => "hello"
},
},
<template 子>
<ccc v-bind="$attrs" v-on="$listeners"></ccc>
</template>
props: {
name,
},
<template 孙>
<div></div>
</template>
created() {
this.$emit('send', 'hello') //可以触发aaa的send函数
}
- 当要传递的数据很多时,就需要在中间的每个组件都重复写很多遍,反过来从后代组件向祖先组件使用 events 传递也会有同样的问题。使用 $attrs 和 $listeners 就可以简化这样的写法。
- $ attrs 会包含父组件中没有被 prop 接收的所有属性(不包含class 和 style 属性),可以通过 v-bind=“$attrs” 直接将这些属性传入内部组件。
- $ listeners 会包含所有父组件中的 v-on 事件监听器 (不包含 .native 修饰器的) ,可以通过 v-on=“$listeners” 传入内部组件。
- 这种方式的传值虽然说不常用,可读性不是很好。但其对于组件层级嵌套比较深,使用props会很繁琐,或者项目比较小,不太适合使用 Vuex 的时候,可以考虑用它
7.eventBus
- 对于没有必要引入 vuex 的情况下,可以使用 eventBus,eventBus 可以实现任意两个组件间的通信。
//新建一个eventBus.js
import Vue from 'vue';
export default new Vue();
//A组件
<template>
<div>
<bbb></bbb>
<ccc></ccc>
</div>
</template>
//B组件
import Bus from 'eventBus.js';
export default {
//B 组件参num: 1
methods: {
ck() {
Bus.$emit('send', {
num: this.num++
})
}
}
}
//C组件 接收事件
import Bus from 'eventBus.js';
export default {
//C 组件参count : 1
created() {
Bus .$on('send', param => {
this.count = this.count + param.num; // 在A组件中 得到B组件参数+C组件参数
})
}
}
它的实现思想也很好理解,在要相互通信的两个组件中,都引入同一个新的vue实例,然后在两个组件中通过分别调用这个实例的事件触发和监听来实现通信。
如果想移除事件的监听, 可以这样操作:
- import { eventBus } from ‘event-bus.js’
- EventBus.$off(‘addition’)
8.$root 访问根实例
- 通过 $root,任何组件都可以获取当前组件树的根 Vue 实例,通过维护根实例上的 data,就可以实现组件间的数据共享。
//main.js 根实例
new Vue({
el: '#app',
store,
router,
// 根实例的 data 属性,维护通用的数据
data: function () {
return {
rootData: ''
}
},
components: { App },
template: '<App/>',
});
//组件A
created() {
this.$root.rootData= '参数'
}
//组件B
<template>
<div> {{ $root.rootData}}</div>
</template>
通过这种方式,虽然可以实现通信,但在应用的任何部分,任何时间发生的任何数据变化,都不会留下变更的记录,这对于稍复杂的应用来说,调试是致命的,不建议在实际应用中使用。
9.Vuex( Vuex就不在这细说了 , 抽空单独写一篇 )
-
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。对一个中大型单页应用来说是不二之选。
-
state:用于数据的存储,是store中的唯一数据源
-
getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
-
mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件
-
actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
-
modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
10.简单的 Store 模式
//store.js
var store = {
debug: true,
state: {
author: 'yushihu!'
},
setAuthorAction (newValue) {
if (this.debug) console.log('setAuthorAction triggered with', newValue)
this.state.author = newValue
},
deleteAuthorAction () {
if (this.debug) console.log('deleteAuthorAction triggered')
this.state.author = ''
}
}
上面代码原理就是,store.js文件暴露出一个对象 store,通过引入 store.js 文件 各个页面来共同维护这个store对象
和 Vuex 一样,store 中 state 的改变都由 store 内部的 action 来触发,并且能够通过 console.log() 打印触发的痕迹。这种方式十分适合在不需要使用 Vuex 的小项目中应用。
11.localStorage / sessionStorage
- 通过window.localStorage.getItem(key)获取数据
- 通过window.localStorage.setItem(key,value)存储数据
这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。
注意用JSON.parse() / JSON.stringify() 做数据格式转换, localStorage / sessionStorage可以结合vuex, 实现数据的持久保存,同时使用vuex解决数据和状态混乱问题
12.provide / inject
//祖先组件
export default {
name: "father",
provide() {
return {
foo: 'hello'
}
},
}
//后代组件 注入foo, 直接当做this.foo来用
export default {
inject:['foo'],
}
一旦注入了某个数据,比如上面示例中的 foo,
那这个组件中就不能再声明 foo 这个数据了,因为它已经被父级占有。
provide 和 inject 绑定并不是可响应的,这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。因为对象是引用类型。
13.slot
//父组件
<template 父>
<div>
<aaa v-slot="send">{{ send.aaa.name }}</aaa >
</div>
</template>
//子组件
<template 子>
<div> <slot :user="aaa"></slot> </div>
</template>
export default{
data(){
return {
aaa:{ name:"A" }
}
}
}
子组件的数据通过插槽的方式传给父组件使用,然后再通过插槽插回来
14.dispatch 和 broadcast(vue 在2.0版本就已经移除了就不说了)
end
![在这里插入图片描述](https://img-blog.csdnimg.cn/d99f2ff9e3c34cb0b56ae620411a8830.png