zoukankan      html  css  js  c++  java
  • 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数据, 得到最新保存的数据进行显示

  • 相关阅读:
    错误 1 类,结构或接口成员声明中的标记"="无效
    转asp.net中的App_GlobalResources和App_LocalResources使用
    input type=file 上传文件样式美化(转载)
    Postman Post请求上传文件
    vuex 、store、state (转载)
    ES5、ES2015、ECMAScript6(转载)
    axios 用法简介(转载)
    js中const,var,let区别(转载)
    C#开发微信公众平台-就这么简单(附Demo)转载
    什么是 Native、Web App、Hybrid、React Native 和 Weex?(转载)
  • 原文地址:https://www.cnblogs.com/fsg6/p/13571192.html
Copyright © 2011-2022 走看看