vue组件间高级通信

一,组件间高级通信,准备代码

准备工作
    1、把定义好的communication组件注册到我们的前台项目当中
    2、修改路由配置,路由组件全是函数模式,路由懒加载
    3、注册element-ui的button组件

路由配置

{
    path: "/communication",
    component: () => import("@/pages/Communication/Communication"),
    children: [
      {
        path: "event",
        component: () => import("@/pages/Communication/EventTest/EventTest"),
        meta: {
          isHideFooter: true,
        },
      },
      {
        path: "model",
        component: () => import("@/pages/Communication/ModelTest/ModelTest"),
        meta: {
          isHideFooter: true,
        },
      },
      {
        path: "sync",
        component: () => import("@/pages/Communication/SyncTest/SyncTest"),
        meta: {
          isHideFooter: true,
        },
      },
      {
        path: "attrs-listeners",
        component: () =>
          import("@/pages/Communication/AttrsListenersTest/AttrsListenersTest"),
        meta: {
          isHideFooter: true,
        },
      },
      {
        path: "children-parent",
        component: () =>
          import("@/pages/Communication/ChildrenParentTest/ChildrenParentTest"),
        meta: {
          isHideFooter: true,
        },
      },
      {
        path: "scope-slot",
        component: () =>
          import("@/pages/Communication/ScopeSlotTest/ScopeSlotTest"),
        meta: {
          isHideFooter: true,
        },
      },
    ],
  },

然后到该组件,http://localhost:8080/#/communication

 

Vue组件间通信深入
1. 组件间通信(传值)的多种方式
1. 组件间通信最基本方式: props
2. 组件间通信高级1: vue自定义事件与事件总线
3. 组件间通信高级2: v-model深入
4. 组件间通信高级3: 属性修饰符sync
5. 组件间通信高级4: $attrs与$listeners
6. 组件间通信高级5: $children与$parent
7. 组件间通信高级6: 作用域插槽slot-scope
8. 组件间通信高级7: vuex
2.1. 组件间通信最基本方式: props

props 子组件声明接收属性三种写法 【‘todos’】 { todos:Array} { todos:{type:Array,default:[]}}
父子之间
父可以给子传递 非函数和函数
传非函数数据 就是父给子
传函数数据 本质是父想要子的数据


特殊:
路由配置 props(三种) 路由组件之间没有标签,但是可以把参数通过路由映射为属性

 

 

1.1. 组件间通信高级1: vue自定义事件与事件总线

1.1.1. 什么条件下绑定的原生DOM事件监听?

1. 给html标签绑定dom事件监听:

<div @click="handleClick">

2. 给组件标签绑定dom事件监听(使用.native):

<MyCommponent @click.native="handleClick">

原生dom事件在html标签和组件标签上的区别 (Event1组件测试)
在html标签上添加就是原生的dom事件
在组件标签上添加就是自定义事件,想成为原生的事件得添加修饰符.native,就是把原生dom事件添加到组件根元素上

1.1.2. 什么条件下绑定的vue自定义事件监听?

1. 自定义事件名:  <MyComponent @xxx="handleClick2">

2. 与dom事件名同名: <MyComponent @click="handleClick">

 

1.1.3. 如何触发/分发事件?

1. 触发原生DOM事件:

(1)    浏览器自动分发: 用户操作界面对应元素时, 浏览器自动分发/触发相应的事件

(2)    传递数据: 固定为包含所有相关数据的对象event

2. 触发vue自定义事件:

(1)    必须编码分发: vm.$emit(事件名, 数据)

(2)    传递数据: 由emit()事件名后面的参数指定, 当然也可以不指定

(3)    事件监听回调: 默认变量$event就是分发事件时指定的数据

 

编码测试

1. Event1.vue
<template>
  <div style="background: #aaa">
    <h2>Event1组件</h2>
    <span>其它内容</span>
  </div>
</template>

2. Event2.vue
<template>
  <div>
    <h2>Event2组件</h2>
    <button @click="$emit('click', 123)">分发自定义click事件</button><br>
    <button @click="$emit('xxx', 'atguigu')">分发自定义xxx事件</button><br>
  </div>
</template>

3. EventTest.vue
<template>
  <div>
    <h1>EventTest组件</h1>
    <button @click="test1">测试原生事件</button>

    <hr>
    <Event1 @click.native="test2($event)"/>

    <hr>
    <Event2 @click="test3" @xxx="test4($event)" />

  </div>
</template>

<script type="text/ecmascript-6">
  import Event1 from './Event1.vue'
  import Event2 from './Event2.vue'

  export default {

    methods: {
      test1 (event) {
        alert('test1() ' + event.target.innerHTML)
      },
      test2 (event) {
        alert('test2() ' + event.target.innerHTML)
      },
     
      test3 (data) {
        alert('click test3()  ' + data )
      },
      test4 (data) {
        alert('xxx test4()  ' + data )
      }
    },

    components: {
      Event1,
      Event2,
    }
  }
</script>

 

 

 

 

1.1.4. 全局事件总线

1. 前置知识:

(1)    Vue原型对象上有3个事件处理的方法: $on() / $emit() / $off()

(2)    组件对象的原型对象是vm对象: 组件对象可以直接访问Vue原型对象上的方法

(3)    实现任意组件间通信

2. 实现

(1)    将入口js中的vm作为全局事件总线对象:

beforeCreate() {

Vue.prototype.$bus = this

}

(2)    分发事件/传递数据的组件: this.$bus.$emit('eventName', data)

(3)    处理事件/接收数据的组件: this.$bus.$on('eventName', (data) => {})

 

 

1.1. 组件间通信高级2: v-model深入理解

1.1.1. v-model本质

1. 官方文档: 在组件中使用 v-model

2. 本质是动态value属性与input事件监听的语法糖

3. 在原生input标签上使用

<input v-model="searchText">

等价于

<input
  :value="searchText"
  v-on:input="searchText = $event.target.value"
>

 

4. 在组件标签上使用

<custom-input v-model="searchText">

 

等价于

<custom-input
  :value="searchText"
  v-on:input="searchText = $event"
></custom-input>

 

1.1.2. 利用v-model能做什么呢?

1. v-model不仅能实现原生标签的双向数据绑定, 也能实现组件标签的双向数据绑定

2. 实现父子组件间数据双向同步

3. 一般用于封装带表单项的复用性组件

4. elment-ui中的Input/CheckBox/Radio/Select等表单项组件都封装了v-model

1、html input v-model的本质
        :value = “data”  //读取数据
        @input = "data = $event.target.value"  //写数据
        

    2、组件标签上 v-model本质
        :value = "data"  父组件传递属性给子组件,子组件需要接受
        @input = "data = $event"
        数据在父组件当中

        
        子组件当中必须这样写
        先接受props:['value']
        
        子组件表单类元素
            :value = "value"
            @input = "@emit('input',$event.target.value)"

 

1.1.3. 编码测试

 @input属于原生dom事件

ModelTest.vue父组件在子组件CustomInput.vue设置v-model,此时,子组件的name改变,导致父组件页的name跟着变化,那父组件的name改变,props传递的值给子组件也跟着变化

1. ModelTest.vue

<template>
  <div>
    <input type="text" v-model="name">
    <span>{{name}}</span>
    <hr>

    <input type="text" :value="name2" @input="name2=$event.target.value">
    <!--<input type="text" :value="name2" @input="handleInput">-->
   
<span>{{name2}}</span>
    <hr>

    <custom-input v-model="name3"></custom-input>
    <span>{{name3}}</span>
    <hr>
$event为子组件传递过来的数据,然后赋值给name4,不是事件对象
       <custom-input :value="name4" @input="name4=$event"></custom-input>
    <!--<custom-input :value="name4" @input="handleInput2"></custom-input>-->
   
<span>{{name4}}</span>
  </div>
</template>

<script type="text/ecmascript-6">
  import CustomInput from './CustomInput.vue'
 
export default {
    data () {
      return {
        name: 'Tom',
        name2: 'Jack',
        name3: 'Bob',
        name4: 'Cat'
     
}
    },

    methods: {
      handleInput (event) {
        this.name2 = event.target.value
     
},

      handleInput2 (value) {
        this.name4 = value
      }
    },

    components: {
      CustomInput
    }
  }
</script>

 

2. CustomInput.vue

<template>
  <div style="border: 1px solid red">
    <span>input包装组件:</span>
    <input type="text" :value="value" @input="$emit('input', $event.target.value)">
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    props: ['value']
  }
</script>

 

 

1.1.1. 利用sync能做什么呢?

  1. 1.       实现父子组件间数据双向同步
  2. 2.       常用于封装可复用组件
  3. 3.       element-ui的Dialog就利用sync来实现组件的隐藏
实现父子组件双向数据同步问题
    和 v-model 实现效果几乎一样
    v-model一般用于带表单项的组件
    sync属性修饰符一般用于不带表单项的组件
    
    父组件给子组件属性传递数据后面添加.sync
    子组件修改数据 需要分发事件@click = $emit("update:属性名",要更新的数据)

    本质上还是自定义事件

  <!-- <SpuForm v-show="isShowSpuForm" :visible="isShowSpuForm" @update:visible="isShowSpuForm=$event"></SpuForm> -->
    父组件给子组件传递  isShowSpuForm数据,
<SpuForm v-show="isShowSpuForm" :visible.sync="isShowSpuForm"></SpuForm>
    
  <el-button @click="$emit('update:visible',false)">返回</el-button>

 

1.1.2. 编码测试

需求: 假设这么一个场景:假设小明的父亲有1000块钱,小明一次花掉100元,就是点击一次花钱按钮父亲的钱减少100

父组件SyncTest.vue,通过sync修饰符,将total传递给子组件child, 然后子组件数据变动,父组件的数据也跟着变动

 

 

  1. SyncTest.vue
<template>
  <div>
    小明的爸爸现在有 {{ total }} 元
    <hr>

    <h2>不使用sync修改符</h2>
    <Child :money="total" v-on:update:money="total = $event"/>
    <hr>

    <h2>使用sync修改符</h2>
    <Child :money.sync="total"/>
    <hr>

    <h2>使用v-model修改符</h2>
    <Child2 v-model="total"/>
    <hr>
  </div>
</template>

<script type="text/ecmascript-6">
  import Child from './Child.vue'
 
import Child2 from
'./Child2.vue'
 
export default {
    data () {
      return {
        total: 1000
      }
    },

    components: {
      Child,
      Child2
    }
  }
</script>

 

  1. Child.vue
<template>
  <div>
    <span>小明每次花100元</span>
    <button @click="$emit('update:money', money - 100)">花钱</button>
    爸爸还剩 {{money}} 元
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    props: ['money']
  }
</script>

 

  1. Child2.vue
<template>
  <div>
    <span>小明每次花100元</span>
    <button @click="$emit('input', value - 100)">花钱</button>
    爸爸还剩 {{value}} 元
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    props: ['value']
  }
</script>

 

 

1.1. 组件间通信高级4: $attrs与$listeners

$attrs和$linsteners(六)  
    本质就是父组件中给 子组件传递的所有属性组成的对象及自定义事件方法组成的对象
子组件 <HintButton   type="danger" icon="el-icon-delete" title="删除"  @heihei="test1" @click="test2"></HintButton>
子组件内部:
<a href="javascript:;" :title="title">
    <el-button v-bind="$attrs" v-on="$listeners"></el-button>
  </a>
 
    $attrs 如果不声明props 那么子组件当中是可以看到  如果声明了哪个属性,那么那个属性在$attrs当中看不到
    它会排除 props声明接收的属性 以及class style

    可以通过v-bind 一次性把父组件传递过来的属性添加给子组件
    可以通过v-on   一次性把父组件传递过来的事件监听添加给子组件

    对一个组件进行二次封装


8、    element-ui的button添加click事件会触发,添加dblclick就不会触发的问题
        
    element-ui的button  子组件内部触发了这个单击事件
<el-button type="primary" @click="test1">测试</el-button>
    element-ui的button  子组件内部没有触发这个双击事件
   <el-button type="danger" icon="el-icon-delete"  @dbclick="test1">双击事件</el-button>


    扩展双击点击触发element-ui button事件,使用原生。native

 

1.1.1. 理解

1. $attrs: 排除props声明, class, style的所有组件标签属性组成的对象

2. $listeners: 级组件标签绑定的所有自定义事件监听的对象

3. 一般: v-bind与$attrs配合使用, v-bind与$listeners配合使用

1.1.2. 利用它们能做什么?

  1. 1.       在封装可复用组件时

(1)     从父组件中接收不定数量/名称的属性或事件监听

(2)     在组件内部, 并传递给它的子组件

  1. 2.       element-ui中: Input就使用了v-on与$attrs来接收不定的属性传递给input

1.1.3. 编码测试

需求: 对el-button进行二次封装, 实现使用按钮能有hover的文本提示

 

 

 

1. LinkButton.vue

<template>
  <a href="javascript:" :title="title">
    <el-button v-bind="$attrs" v-on="$listeners"></el-button>
  </a>
</template>

<script>
export default {
  name'LinkButton',

  props: ['title'],

  mounted () {
    // console.log(this.$attrs, this.$listeners)
 
}
}
</script>

 

2. AttrsListenersTest.vue

<template>
  <div>
    <LinkButton type="primary" size="mini" icon="el-icon-plus"
     
title="添加" @click="add" @xxx="add"></LinkButton>

   
//双击事件     <LinkButton type="primary" size="mini" icon="el-icon-plus"
     
title="添加" @dblclick.native="add2"></LinkButton>

   
    <LinkButton type="info" size="mini" icon="el-icon-edit"
     
title="修改" @click="update"></LinkButton>
   
    <LinkButton type="danger" size="mini" icon="el-icon-delete"
     
title="删除" @click="remove"></LinkButton>
  </div>
</template>

<script type="text/ecmascript-6">
  import LinkButton from './LinkButton'
 
export default {
    name: 'AttrsListenersTest',

    methods: {
      add () {
        alert('添加数据')
      },
      add2 () {
        alert('响应双击')
      },
      update () {
        alert('更新数据')
      },
      remove () {
        alert('删除数据')
      },
    },

    components: {
      LinkButton
    }
  }
</script>

 

1.1. 组件间通信高级5: $children与$parent属性

1.1.1. 理解

1. $children: 所有直接子组件对象的数组(注意不确定顺序)

2. $parent: 父组件对象

3. $refs: 包含所有有ref属性的标签对象或组件对象的容器对象

$parent 和 $children以及$ref(七)
    $children:所有子组件对象的数组
  this.$children.forEach(item=>{
        item.money -=money
      })
    $parent:代表父组件对象
 this.$parent.money +=money

    父组件当中可以通过$children找到所有的子组件去操作子组件的数据(当然可以找孙子组件)
    子组件当中可以通过$parent找到父组件(当然可以继续找爷爷组件)操作父组件的数据

 

1.1.2. 利用它们能做什么?

1. 能方便的得到子组件/后代组件/父组件/祖辈组件对象, 从而更新其data或调用其方法

2. 官方建议不要大量使用, 优先使用props和event

3. 在一些UI组件库定义高复用组件时会使用$children和$parent, 如Carousel组件

 

 

1.1.1. 扩展: Vue组件Mixin技术

1. 官方文档: mixin

2. 什么时候使用: 当多个组件的JS配置部分有一些相同重复的代码时

3. 本质: 就是Vue的mixin技术是实现Vue组件的JS代码复用, 简化编码的一种技术

 

1.1.2. 编码测试

 

 

1. ChildrenParentTest.vue

<template>
  <div>
    <h2>BABA有存款: {{money}}</h2>
    <button @click="borrowMoney1">找小明借钱100</button><br>
    <button @click="borrowMoney2">找小红借钱150</button><br>
    <button @click="borrowMoney3">找所有孩子借钱200</button><br>
    <hr>

    <Son ref="son"/>
    <hr>

    <Daughter ref="dau"/>
  </div>
</template>

<script>
import Son from './Son'
import Daughter from
'./Daughter'

export default {
  name'ChildrenParentTest',
  data () {
    return {
      money: 1000
    }
  },

  methods: {
   
    borrowMoney1 () {
      // this.$refs.son.money -= 100
     
this.$refs.son.pullMoney(100)
      this.money += 100
    },

    borrowMoney2 () {
      this.$refs.dau.pullMoney(150)
      this.money += 150
    },

    borrowMoney3 () {
      // this.$refs.son.money -= 200
      // this.$refs.dau.money -= 200
      // this.money += 200
     
console.log(this.$children)
      // 通过$children得到所有的直接子组件对象的数组
     
this.$children.forEach(child => {
        child.money -= 200
        this.money += 200
      })
    },
  },

  components: {
    Son,
    Daughter
  }
}
</script>

 

2. Son.vue

<template>
  <div>
    <h3>儿子小明: 有存款: {{money}}</h3>
    <button @click="gaveMoney(50)">给BABA钱: 50</button>
  </div>
</template>

<script>
import {cpMixin} from './mixins'
export default {
  name'Son1',
  mixins: [cpMixin],
  data () {
    return {
      money: 30000
    }
  },

  methods: {
   
  }
}
</script>

 

3. Daughter.vue

<template>
  <div>
    <h3>女儿小红: 有存款: {{money}}</h3>
    <button @click="gaveMoney(100)">给BABA钱: 100</button>
  </div>
</template>

<script>
import {cpMixin} from './mixins'

export default {
  name'Daughter',
 
  mixins: [cpMixin],

  data () {
    return {
      money: 20000
    }
  },

  methods: {

  }
}
</script>

 

4. mixins.js

/*
*
定义项目中的多个可复用的组件配置
*/
export const cpMixin = {

  methods: {
    /*
   
花钱, 由父组件调用
    */
   
pullMoney (money) {
      this.money -= money
    },

    /*
   
给父组件钱
    */
   
gaveMoney (money) {
      // 通过$parent得到父组件对象, 从而更新其数据
     
this.$parent.money += money
      this.money -= money
    }
  }
}

 

 

1.1. 组件间通信高级6: 作用域插槽slot-scope

1.1.1. 理解

1. 官方文档: 作用域插槽(scoped slots)

2. 什么情况下使用作用域插槽

(1)     父组件需要向子组件传递标签结构内容

(2)     但决定父组件传递怎样标签结构的数据在子组件中

1.1.2. 利用slot-scope能做什么呢?

1. 对于封装列表之类的组件特别需要

2. element-ui中: Table组件中就用到了slot-scope

1.1.3. 编码测试

需求: 封装列表List组件

效果一: 显示TODO列表时, 已完成的TODO为绿色

效果二: 显示TODO列表时, 带序号, TODO的颜色为蓝绿搭配

 

ScopeSlotTest.vue

<template>
  <div>
    <h2>效果一: 显示TODO列表时, 已完成的TODO为绿色</h2>
    <!-- 将todos传递给子组件list -->
    <!-- <List :todos="todos">

      //子组件传递过来的数据(todo)被一个对象slotProps接收
      <template slot-scope="slotProps">
        <span v-if="slotProps.todo.isComplete" style="color:hotpink">{{slotProps.todo.text}}</span>
        <span v-else>{{slotProps.todo.text}}</span>
      </template>
    </List> -->
    <hr>

    <h2>效果二: 显示TODO列表时, 带序号, TODO的颜色为蓝绿搭配</h2>
    <List :todos="todos">
      <template slot-scope="slotProps">
        <span :style="{color:slotProps.index % 2===1?'blue':'green'}">{{slotProps.index+1}}{{slotProps.todo.text}}</span>
      </template>
    </List>
  </div>
</template>

<script type="text/ecmascript-6">
  import List from './List'
  export default {
    name: 'ScopeSlotTest',
    data () {
      return {
        todos: [
          {id: 1, text: 'AAA', isComplete: false},
          {id: 2, text: 'BBB', isComplete: true},
          {id: 3, text: 'CCC', isComplete: false},
          {id: 4, text: 'DDD', isComplete: false},
        ]
      }
    },

    components: {
      List
    }
  }
</script>

 

List.vue

<template>
  <ul>
    <li v-for="(todo, index) in todos" :key="todo.id">
      <!-- 将todo传递给父组件 ,slot只是占位置,将父组件的模板占据过来,没有实际意义-->
      <slot :todo="todo" :index="index">
        {{todo.text}}
      </slot>
    </li>
  </ul>
</template>

<script>
export default {
  name: 'List',
  props: {
    todos: Array
  }
}
</script>

 

1.1. 组件间通信高级7: vuex

1. vuex用来统一管理多个组件共享的状态数据

2. 任意要进行通信的2个组件利用vuex就可以实现

(1)     A组件触发actionmutation调用, 将数据保存到vuex的状态中

(2)     B组件读取vuex中的stategetters数据, 得到最新保存的数据进行显示

 

posted @ 2020-08-27 14:21  全情海洋  阅读(365)  评论(0编辑  收藏  举报