1. props
- 父子通信最常用的方式
- 子组件接收到数据后,不能直接修改父组件的数据,会报错
- 代码实现
//父组件
<child :msg="msg" />
//子组件
export default{
//写法1:用数组接收
props:['msg'];
//写法2:用对象接收,可以限定接收的数据类型,默认值等
props:{
msg:{
type:String,
default:"这是默认值"
}
}
}
2. .sync
- 对props进行双向绑定,即子组件可修改父组件传入的数据
- 于2.0移除,2.3.0重新引入,作为编译的语法糖,被自定扩展为一个自动更新父组件属性的v-on监听器
//例如 <child :foo.sync='msg' />
//会被扩展为
//<child :foo='bar' @update:foo='val=>bar=val' />
//当子组件更新foo时,需要触发一个更新事件
this.$emit("update:foo",newValue)
- 代码实现
//父组件
<child :msg.sync = 'msg' />
//子组件
<input type="text" v-model='data' @input='fn' />
export default{
props:['msg'],
data(){
return{
data:this.msg //定义一个变量接收props传下来的内容,后面说为什么
}
},
methods:{
fn(e){
this.$emit("update:foo",e.target.value)
}
},
watch:{
msg(){
this.data = this.msg
}
}
}
- 问题说明
- 为什么要用变量接收props传递的数据?
- 如果我们不用变量接收的话,直接使用是可以实现父子双向通信的,但问题是vue不建议我们直接修改props的值,所以会有警告⚠️。于是我们可以使用一个变量去接收props的值
- 基于问题1出现的另一个问题,修改父组件数据,子组件无变化
- 原因就是我把子组件接收的数据用变量(data)接收导致的,data只会赋值一次,所以可以使用watch监听props中的数据,每次发生变化都将该值赋值给data
3. v-model
- 和.sync类似,父组件通过v-model给子组件绑定一个value属性和input事件,子组件通过$emit修改副组件的数据
- 代码实现
//父组件
<child v-model='msg' />
//扩展为
<child :value='msg' @input ='$event.target.value' />
//子组件
export default {
props:['value'],
methods:{
handlerChange(e){
this.$emit("input", e.target.value)
}
},
}
4. ref
- 如果把ref绑定给普通dom元素上,引用指向的就是该dom元素
- 如果绑定在子组件上,引用的指向就是子组件实例,然后父组件通过ref主动获取子组件的属性或调用子组件的方法
- 代码实现
//子组件
data(){
return{
msg:'qwe' //定义数据
}
},
methods:{
// 定义方法
fn(){
alert(this.msg)
}
}
//父组件
<child ref="child" />
<button @click='getCilck'>调用子组件函数</button>
export default{
data(){
return{
msg:""
}
},
mounted(){
//组件挂载之后获取子组件数据
this.getCilck()
},
methods:{
getCilck(){
const child = this.$refs.child
this.msg = child.msg //获取子组件数据
child.fn() // 调用子组件的方法
}
}
5. $attrs/$listeners
- 多层嵌套组件传递数据时,如果只是传递数据,不做中间处理就可以使用,比如爷孙组件通信
- $attrs:包含父作用域除class和style的非props属性
- $listeners:包含父作用域里.native外的监听事件集合
- 使用方法:
- 爷——>孙传递数据,子组件不声明props接收,直接v-bind="$attrs",绑定给孙组件,孙组件声明props接收
- 爷——>孙传递方法,同样先传给子组件,子组件不声明props接收,通过v-on="$listeners"绑定给孙组件,孙组件声明props接收
- 代码实现
//父组件
<child :name='name' :toSun="toSun" />
//子组件
<sun v-bind="$attrs" v-on="$listeners" />
//孙组件
export default{
props:['name','toSun']
}
6. $children/$parent
- $children:获取到一个包含所有子组件的组件对象数组,可以直接拿到子组件中的所有数据和方法
- $parent:获取到一个父节点的组件对象,同样包含父节点中的所有数据和方法
- 代码实现
//父组件获取子组件的数据和方法,n代表子组件下标
this.$children[n].msg
this.$children[n].fn()
//子组件获取父组件的数据和方法
this.$parent.msg
this.$parent.fn()
7. provide/inject
- vue2.2.0新增的API
- provide可以在祖先组件中指定想传给后代的数据和方法,然后在任何后代组件中都可以用inject来接收provide提供的数据和方法
- vue不会对provide中的变量进行响应式处理。所以,要想接收的变量是响应式的,provide提供的变量本身就需要是响应式的
- provide/inject实现全局状态管理
- 在根组件的provide中传入变量,后代组件就可以通过inject接收
- 相比vuex优点:简单暴力
- 缺点:不可溯源
- 官网实例
//父组件
var provide={
provide:{
foo:'bar'
}
}
//子组件注入foo
var child = {
inject:['foo'],
mounted(){
console.log(this.foo)
}
}
- 代码实现
//父组件
provide(){
return{
father:this, //子组件通过这个this可以获取父组件的所有数据和方法
toChild:this.toChild, //methids中定义的函数
name:this.name //data中定义的数据
}
}
//子组件
inject:['father','toChild','name'],
mounted(){
console.log(this.father.name) //通过父组件的this获取到name
this.toChild() //调用父组件传递的方法
console.log(this.name) //调用父组件传递的数据
}
8. EventBus事件总线
-
EventBus是中央事件总线,不管父子,兄弟,跨层级等都可以使用
-
初始化(2种方式)
- 创建一个bus.js文件(局部引入即可)
//bus.js import Vue from 'vue' export default const EventBus = new Vue()- 在main.js中(此时创建的为全局事件总线)
Vue.prototype.$EventBus = new Vue() -
概念图
-
代码实现
//功能:组件1点击"+"或"-"改变data中的point数据,组件2能随着改变而改变
//组件1 发布
data(){
return{
point:0
}
},
methods:{
add(){
this.point++
},
cut(){
this.point--
},
//发布point的方法
fn(){
this.$bus.$emit('sendMsg',{point:this.point}) //参数1:发布事件名,参数2:额外参数
}
},
mounted(){
//页面挂载后发布point
this.fn()
},
watch:{
// 监视point,每次改变都会重新发布
point(){
this.fn()
}
}
//组件2 订阅
created(){
this.$bus.$on('sendMsg',data=>this.point=data.point) //参数1:订阅事件名,参数2:事件回调函数
},
beforeDestory(){
//实例销毁前记得解绑事件总线
this.$bus.$off('sendMsg')
}
- 注意:订阅事件总线需要在生命周期中订阅
9. VUEX
- VUEX是状态管理器,集中式管理所有组件的状态
- 核心概念
- store:用于管理state,getter,mutation,action等,并向外提供一些方法等对象
- state:用于存储数据(原生数据)
- getter:修饰器,用于修饰state数据,通过计算属性方法(走缓存),也可以通过方法访问(不走缓存)
- mutation:用于直接修改state的数据,一般写入同步代码
- action:用于间接修改state等数据,一般写入异步代码
- 配置vuex
- 在src下创建store文件夹,然后创建index.js
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ state: {}, getters:{}, mutations:{}, actions:{} }); export default store;- 修改main.js
import Vue from 'vue' import App from './App.vue' import store from './store' Vue.config.productionTip = false new Vue({ store, render: h => h(App), }).$mount('#app') - 代码实现
- 读取state中的某个属性
//方式1 computed:{ getPoint(){ return this.$store.point } } //方式2 import {mapState} from 'vuex' export default{ computed:{ ...mapState(['point'] } }- 修饰state中的某个属性
//store/index.js const store = new Vuex.Store({ state: { name:'冬梅' }, getters:{ newName(state){ return `马${state.name}` } }, }); export default store; //读取getter //方式1 computed:{ getGetter(){ return this.$store.getters.newName } } //方式2 import {mapGetter} from 'vuex' export default{ computed:{ ...mapGetter(['newName']) } }- 直接修改state中的某个属性
//store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ state: { point:20 }, mutations:{ pointLose(state){ return state.point-- } }, }); export default store; //修改属性 //方式1 lose(){ this.$store.commit('pointLose') } //方式2 import {mapMutations} from 'vuex' export default{ methods:{ ...mapMutations(['pointLise']), lose(){ this.pointLose() } }, }- 间接修改state中的某个属性
//store/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ state: { point:20 }, mutations:{ pointLose(state){ return state.point-- } }, action:{ setPoint(content){ return new Promise(reslove=>{ setTimeout(()=>{ content.commit('pointLose') },1000) } } }, }); export default store; //修改属性 //方式1 async lose2(){ this.$store.dispatch('setPoint') } //方式2 import {mapActions} from 'vuex' export default{ methods:{ ...mapActions(['setPoint']), async lose2(){ await this.setPoint() } }, }
10. slot插槽
- 相当于占位符,它在组件中给html模版占了一个位置
- 插槽又分匿名插槽,具名插槽,作用域插槽
- 代码实现
//父组件
<child >
<!-- 匿名插槽 -->
<template v-slot:default>
{{name}}
</template>
<!-- 具名插槽 -->
<template v-slot:fatherA>
fatherA
</template>
<template v-slot:fatherB>
fatherB
</template>
<!-- 作用域插槽 -->
<template v-slot:fatherC='point'>
C: {{point}}
</template>
</child>
//子组件
<div>1.匿名插槽</div>
<slot></slot>
<div>2.具名插槽</div>
<slot name="fatherA"></slot>
<slot name="fatherB"></slot>
<div>3.作用域插槽</div>
<slot name="fatherC" :point='point'></slot>
11. 自定义事件
- 组件的自定义事件实现的就是子组件向父组件通信的功能,所以,自定义事件的绑定动作需要在父组件中完成
- 组件的自定义事件的触发动作需要在子组件中完成,给谁绑定的事件,就找谁触发
- 代码实现
// 父组件
<template>
<div>
<div class="box">
<div>这是父组件接收的数据:<input type="text" v-model="name"></div>
<sun @sendMsg="getChild" ></sun>
</div>
</div>
</template>
<script>
import sun from "./sun.vue";
export default {
data(){
return{
name:''
}
},
methods:{
getChild(msg){
this.name = msg
}
},
components:{
sun
}
}
</script>
// 子组件
<template>
<div class="box2">
<div>这是子组件中的数据:<input type="text" v-model="fdata" @input="change"></div>
</div>
</template>
<script>
export default {
data(){
return{
fdata:'$emit'
}
},
mounted(){
this.change()
},
methods:{
change(){
this.$emit('sendMsg',this.fdata)
}
}
}
</script>