Vue的组件机制

688 阅读4分钟

本文主要探讨Vue组件三方面的内容

  1. 组件的通信方式:不同关系的组件分别如何传递数据
  2. 插槽:组件如何把内容插入到另一个组件
  3. 组件的设计思想,通过三个实例,探讨vue如何自定义普通组件,动态组件和递归组件

一、vue组件的通信方式

1、父传子:props

<!--Child.vue-->
<template>
    <div>
        <p>来自父组件的静态数据:{{staticMes}} </p>
        <p>来自父组件的动态数据:{{dynamicMes}} </p>
    </div>
</template>
<script>
    export default {
        props: ['staticMes', 'dynamicMes']
    }
</script>
<!--Parent.vue-->
<template>
    <div>
        <Child staticMes="传给子元素的静态数据" :dynamicMes = "parentDynamicMes"/>   
    </div>
</template>
<script>
    import Child from "Child.vue"
    export default {
        components: {
            Child
        },
        data() {
            return {
                parentDynamicMes: '传给子元素的动态数据'
            }
        },
    }
</script>

2、子传父:事件

  • 子组件触发[$emit]事件,把数据作为参数带上
  • 父组件监听事件,并获取参数[数据]
<!--Child.vue-->
<template>
    <div>
        <p>子组件的数据:{{childMes}}</p>
        <button @click="sendMes">把子组件的数据传给父组件</button>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                childMes: '我是子组件的数据'
            }
        },
        methods: {
            sendMes() {
                this.$emit('sendMesFromChild', this.childMes)
            }
        },
    }
</script>
<!--Parent.vue-->
<template>
    <div>
        <Child @sendMesFromChild="receiveMes"/>   
    </div>
</template>
<script>
    import Child from "Child.vue"
    export default {
        components: {
            Child
        },
        methods: {
            receiveMes(mes) {
                console.log('我通过监听事件接收到来自子组件的数据:', mes)
            }
        },
    }
</script>

3、兄弟组件:通过共同的父组件搭桥

通过共同的父组件,用事件把数据传给父组件,再由父组件传给子组件

<!--ChildA.vue-->
<template>
    <div>
        <p>子组件A的数据:{{childAMes}}</p>
        <button @click="sendMes">把子组件的数据传给父组件</button>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                childAMes: '我是子组件A的数据'
            }
        },
        methods: {
            sendMes() {
                this.$parent.$emit('sendMesFromChildA', this.childAMes)
            }
        },
    }
</script>
<!--ChildB.vue-->
<template>
    <div>
        <p>来自子ChildA的数据:{{childAMesShowInChildB}}</p>
    </div>
</template>
<script>
    export default {
        props: ['childAMesShowInChildB']
    }
</script>
<!--Parent.vue-->
<template>
    <div>
        <!--ChildA通过this.$parent触发了sendMesFromChildA事件,所以不需要在这里进行监听,直接在下面的mounted()钩子中进行监听-->
        <ChildA/>
        <ChildB :childAMesShowInChildB="childAMesRecInParent"/>
    </div>
</template>
<script>
    import ChildA from "ChildA.vue"
    import ChildB from "ChildB.vue"
    export default {
        components: {
            ChildA,
            ChildB
        },
        data() {
            return {
                childAMesRecInParent: ''
            }
        },
        mounted () {
            this.$on('sendMesFromChildA', mes => {
                // 监听sendMesFromChildA事件,捕获ChildA组件的数据【通过mes参数】,把mes赋值给this.childAMesRecInParent后,因为ChildB通过props的方式引用了this.childAMesRecInParent,所以ChildA的数据【childAMes】就通过父组件搭桥的方式传到了ChildB【childAMesShowInChildB】。
                this.childAMesRecInParent = mes
            })
        },
    }
</script>

4、祖先和后代:provide && inject

<!--Ancestor.vue-->
<template>
    <div>
        <p>我是祖先组件</p>
        <Mid/>
    </div>
</template>
import Mid from "Mid.vue"    
<script>
    export default {
        components: {
            Mid,
        },
        // 通过provide()抛出数据
        provide() {
            return {
                mesFromAncestor: '一条来自祖先组件的信息'
            };
        },
    }
</script>
<!--Mid.vue-->
<template>
    <div>
        <p>我是中间组件</p>
        <Progeny/>
    </div>
</template>
import Progeny from "Progeny.vue"    
<script>
    export default {
        components: {
            Progeny,
        },
    }
</script>
<!--Progeny.vue-->
<template>
    <div>
        <p>我是后代组件</p>
        <p>接收来自祖先的信息:{{mesFromAncestor}}</p>
    </div>
</template>
<script>
    export default {
        // 通过inject注入
        inject: ['mesFromAncestor'],
    }
</script>

5、任意组件:事件总线 || Vuex

5.1、事件总线:创建一个Bus类[Vue.prototype.$bus = new Vue()]负责事件派发、监听和回调管理

<!--CompA.vue-->
<template>
    <div>
        <p>我是在任意位置的一个A组件</p>
        <button @click="sendMes">发送信息<button/>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                CompAMes: '我是来自CompA的信息'
            }
        },
        methods: {
            sendMes() {
                // 通过this.$bus.$emi触发听事件,并抛出数据
                this.$bus.$emit('busTest', this.CompAMes)
            }
        },
    }
</script>
<!--CompB.vue-->
<template>
    <div>
        <p>我是在任意位置的一个B组件</p>
    </div>
</template>
<script>
    export default {
        mounted() {
            // 通过this.$bus.$on监听事件,并捕获数据
            this.$bus.$on("busTest", mes => {
              console.log(mes);
            });
        }
    }
</script>

5.2、Vuex: 创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更【关于Vuex后续再做探讨

二、vue组件插入内容的方式 —— 插槽

<!--Popup.vue-->
<template>
  <div>
    <!-- 具名插槽 -->
    <slot name="header"></slot>
    <!-- 匿名插槽 -->
    <slot></slot>
    <!-- 作用域插槽 -->
    <slot name="footer" :footerProp="footerTitle"></slot>
  </div>
</template>
<script>
export default {
  data() {
      return {
          footerTitle: {
              confirm: '确定',
              cancel: '取消',
          }
      }
  },
};
</script>
<!--UsePopup.vue-->
<template>
  <div>
    <h3>通过弹窗实例展示插槽用法</h3>
    <Popup>
        <!--具名插槽替换内容-->
        <template #header>
            <h5>温馨提示</h5>
        </template>
        <!--匿名插槽替换内容-->
        <p>您要提交吗?</p>
        <!--作用域插槽:正常来说,父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的,利用作用域插槽可以实现在父级模板中使用子模板的数据-->
        <template v-slot:footer="slotProps">
            <ul>
              <li>{{slotProps.footerProp.cancel}}</li>
              <li>{{slotProps.footerProp.confirm}}</li>
            </ul>
        </template>
    </Popup>
  </div>
</template>

<script>
import Popup from "Popup.vue"
export default {
  components: {
    Popup
  },
};
</script>

三、vue组件设计思想 —— 自定义组件