组件化必会知识点

202 阅读4分钟

组件通信

1、props 和$emit 通信

<!-- index -->
<template>
    <div>
        <h2>组件通信</h2>
        <Child1 msg="props 通信" @custom-event="OnCustomEvent"></Child1>
    </div>
</template>

<script>
import Child1 from '@/components/communication/Child1.vue';
export default {
    components: {
        Child1
    },
    methods:{
        OnCustomEvent(msg){
            console.log(msg);
        }
    }
};
</script>

<!-- Child1 -->
<template>
    <div @click="$emit('custom-event', '自定义事件 - Child1')">
        <h3>Child1 组件</h3>
        <p>{{ msg }}</p>
    </div>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            default: ''
        }
    }
};
</script>

如上例中,需注意

  1. Child1组件中$emit 暴露出来的方法名 => custom-event 与 Parent组件中@XXX(XXX要与Child1组件的方法名一致)
  2. Child1组建中props所传的值,不建议修改,一般都会将数据缓存在data里面。

2、 $parent 和 $children

<!-- index -->
<template>
    <div>
        <h2>组件通信</h2>
        <Child1 msg="props 通信" @custom-event="OnCustomEvent"></Child1>
        <Child2></Child2>
    </div>
</template>

<script>
import Child1 from '@/components/communication/Child1.vue';
import Child2 from '@/components/communication/Child2.vue';
export default {
    components: {
        Child1,
        Child2
    },
    methods:{
        OnCustomEvent(msg){
            console.log(msg);
        }
    },
    mounted(){
        // 需要注意 $children 并不保证顺序,也不是响应式的
        this.$children[1].sendToChild1();
    }
};
</script>


<!-- Child2 -->
<template>
    <div>
        <h3>
            Child2 组件
        </h3>
        <button @click="sendToChild1">给Child1发送消息</button>
    </div>
</template>

<script>
export default {
    methods: {
        sendToChild1() {
            this.$parent.$emit('event-from-child2', 'some msg from child2');
        }
    }
};
</script>

<!-- Child1 -->
<template>
  <div @click="$emit('custom-event', '自定义事件 - Child1')">
    <h3>Child1 组件</h3>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: ""
    }
  },
  mounted() {
    this.$parent.$on("event-from-child2", msg => {
      console.log(msg);
    });
  }
};
</script>

如上例,需注意$children 从0开始,并且不保证顺序,也不是响应式的。比如 有些组件使用v-if,或者异步组件,这样就会导致顺序错乱

3、$bus

// main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
Vue.prototype.$bus = new Vue()

new Vue({
  render: h => h(App),
}).$mount('#app')

<!-- Child2 -->
<template>
    <div>
        <h3>
            Child2 组件
        </h3>
        <button @click="sendToChild1">给Child1发送消息</button>
    </div>
</template>

<script>
export default {
    methods: {
        sendToChild1() {
            this.$bus.$emit('event-from-child2', 'some msg from child2');
        }
    }
};
</script>

<!-- Child1 -->
<template>
  <div @click="$emit('custom-event', '自定义事件 - Child1')">
    <h3>Child1 组件</h3>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: ""
    }
  },
  mounted() {
    this.$bus.$on("event-from-child2", msg => {
      console.log(msg);
    });
  }
};
</script>

如上例,可以发现$bus与$parent 的通信方式有共同性,就是使用了同一个“中间人”进行通信。但发现$parent只适用于子父之间的通信,而$bus是任意关系都可以通信

4、$refs

<!-- index -->
<template>
    <div>
        <h2>组件通信</h2>
        <Child1 msg="props 通信" @custom-event="OnCustomEvent"></Child1>
        <Child2 ref="child2"></Child2>
    </div>
</template>

<script>
import Child1 from '@/components/communication/Child1.vue';
import Child2 from '@/components/communication/Child2.vue';
export default {
    components: {
        Child1,
        Child2
    },
    methods:{
        OnCustomEvent(msg){
            console.log(msg);
        }
    },
    mounted(){
        // 需要注意 $children 并不保证顺序,也不是响应式的
        // this.$children[1].sendToChild1();
        
        // $refs
        this.$refs.child2.sendToChild1();
    }
};
</script>

如上例,可以使用ref指定组件名称,类似于id,名称不可重复,然后使用$refs.refName可以访问组件实例

5、$attrs和$listeners

<!-- index -->
<template>
    <div>
        <h2>组件通信</h2>
        <Child1 msg="props 通信" @custom-event="OnCustomEvent"></Child1>
        <Child2 ref="child2" msg="some message"></Child2>
        <Parent msg="来自Parent 组件的消息" @send-to-grandpa="onFoo"></Parent>
    </div>
</template>

<script>
import Child1 from '@/components/communication/Child1';
import Child2 from '@/components/communication/Child2';
import Parent from '@/components/communication/Parent';
export default {
    components: {
        Child1,
        Child2,
        Parent
    },
    methods: {
        OnCustomEvent(param) {
            console.log(param);
        },
        onFoo(msg) {
            console.log(msg);
        }
    },
    mounted() {
        // 需要注意 $children 并不保证顺序,也不是响应式的
        // this.$children[1].sendToChild1();

        // refs
        this.$refs.child2.sendToChild1();
    }
};
</script>

<!-- Parent -->
<template>
    <div>
        <h3>
            Parent 组件
        </h3>
        <Child2 v-bind="$attrs" v-on="$listeners"></Child2>
    </div>
</template>

<script>
import Child2 from '@/components/communication/Child2.vue';
export default {
    components: {
        Child2
    }
};
</script>


<!-- Child2 -->
<template>
    <div>
        <h3>child2</h3>
        <!-- $attrs -->
        <p>{{ $attrs.msg }}</p>
        <button @click="sendToChild1">给child1发送消息</button>
        <button @click="sendToGrandpa">给grandpa发送消息</button>
    </div>
</template>

<script>
export default {
    methods: {
        sendToChild1() {
            // 利用事件总线发送事件
            // this.$bus.$emit('event-from-child2', 'some msg from child2')
            this.$parent.$emit('event-from-child2', 'some msg from child2');
        },
        sendToGrandpa() {
            this.$emit('send-to-grandpa', '爷爷,我来看你啦');
        }
    }
};
</script>

$attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

$listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

6、inject和provide

<!-- index -->
<template>
    <div>
        <h2>组件通信</h2>
        <Child1 msg="props 通信" @custom-event="OnCustomEvent"></Child1>
        <Child2 ref="child2" msg="some message"></Child2>
        <Parent msg="来自Parent 组件的消息" @send-to-grandpa="onFoo"></Parent>
    </div>
</template>

<script>
import Child1 from '@/components/communication/Child1';
import Child2 from '@/components/communication/Child2';
import Parent from '@/components/communication/Parent';
export default {
    provide() {
        return {
            bar: 'bar'
        };
    },
    components: {
        Child1,
        Child2,
        Parent
    },
    methods: {
        OnCustomEvent(param) {
            console.log(param);
        },
        onFoo(msg) {
            console.log(msg);
        }
    },
    mounted() {
        // 需要注意 $children 并不保证顺序,也不是响应式的
        // this.$children[1].sendToChild1();

        // refs
        this.$refs.child2.sendToChild1();
    }
};
</script>

<!-- Child2 -->
<template>
    <div>
        <h3>
            Child2 组件
        </h3>
        <p>attrs : {{ $attrs.msg }}</p>
        <!-- inject/provide -->
        <!-- <p>{{ bar }}</p> -->
        <p>{{ bar1 }}</p>
        <button @click="sendToChild1">给Child1发送消息</button>
        <button @click="sendToGrandpa">给grandpa发送消息</button>
    </div>
</template>

<script>
export default {
    // inject: ['bar'], // 类似于props
    inject: {
        bar1: {
            from: 'bar',
            default: 'barrrrrr'
        }
    },
    methods: {
        sendToChild1() {
            // 事件总线发送事件 bus
            this.$bus.$emit('event-from-child2', 'some msg from child2');

            // parent
            // this.$parent.$emit('event-from-child2', 'some msg from child2');
            // this.$emit('foo', '爷孙通信,父亲转发');
        },
        sendToGrandpa() {
            this.$emit('send-to-grandpa', '爷爷,我来看你啦');
        }
    }
};
</script>

详情可看官方文档 provide/inject

7、Vuex

创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更

插槽

插槽语法是Vue实现内容分发API,用于复合组件开发。该技术在通用组件库开发中有大量应用。

<!-- index -->
<template>
    <div>
        <h2>插槽</h2>
        <Layout>
            <!-- 具名插槽 -->
            <template v-slot:header>好好学习,追上距离</template>
            <!-- 匿名插槽 -->
            <template>content...</template>
            <!-- 作用域插槽 -->
            <template v-slot:footer="{ fc }">{{ fc }}</template>
        </Layout>
    </div>
</template>

<script>
import Layout from '@/components/slots/Layout.vue';
export default {
    components: {
        Layout
    }
};
</script>


<!-- Layout -->
<template>
    <div>
        <div class="header">
            <slot name="header"></slot>
        </div>
        <div class="body">
            <slot></slot>
        </div>
        <div class="footer">
            <slot name="footer" :fc="footerContent"></slot>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            remark: [
                '好好学习,天天向上',
                '学习永远不晚',
                '学习知识要善于思考,思考,再思考',
                '学习的敌人是自己的满足,要认真学习一点东西,必须从不自满开始',
                '构成我们学习最大障碍的是已知的东西,而不是未知的东西',
                '在今天和明天之间,有一段很长的时间;趁你还有精神的时候,学习迅速办事',
                '三人行必有我师焉;择其善者而从之,其不善者而改之'
            ]
        }
    },
    computed: {
        footerContent() {
            return this.remark[new Date().getDay() - 1];
        }
    }
}
</script>