组件通信

2.3k 词
组件通信

组件通信#

组件通信,就是指组件与组件之间的数据传递

  • 组建的数据是独立的,无法直接访问其他组件的数据
  • 想使用其他组件的数据就需要组件通信

不同的组件关系和组件通信方案分类#

组件关系分类:#

  1. 父子关系
  2. 非父子关系

组件通信解决方案#

  1. 父子关系: props$emit
  2. 非父子关系:
    • provide & inject
    • eventbus
  3. 通用解决方案: Vuex (适合复杂业务场景)

父子组件通信#

  1. 父组件通过 props 将数据传递给子组件
  2. 子组件利用 $emit 通知父组件修改更新

父组件传数据给子组件#

准备子组件SonBox.vue

父组件准备的数据用 v-bind 以属性方式绑定到子组件标签上,当然v-bind可以省略只写冒号

<!-- App.vue -->
<template>
    <div class="App">
        App组件(父组件)
        <!-- 1.给组件标签,添加属性的方式传值 -->
        <SonBox v-bind:title="myTitle" :abc="father"></SonBox>
    </div>
</template>
<script>
import SonBox from './components/SonBox.vue';
export default {
    data() {
        return {
            myTitle: 'a son',
            father: 'App.vue',
        };
    },
    components: {
        SonBox,
    },
};
</script>

子组件要使用父组件的数据,就需要使用 props 进行接收数据, 注意props中列表项的值要与子组件中绑定的属性名一致

传值和接收值完成后就可以在子组件中正常使用了

<!-- SonBox.vue -->
<template>
    <div>
        <!-- 3.渲染使用 -->
        子组件{{ title }}
        <div>父组件是{{ abc }}</div>
    </div>
</template>
<script>
export default {
    // 2.通过props进行接收
    props: ['title', 'abc'],
};
</script>

子组件传数据给父组件#

子组件利用 $emit 通知父组件,进行修改更新

子组件调用父组件传递的回调函数,将数据作为参数传递给该函数

<!-- SonBox.vue -->
<template>
    <div>
        <!-- 1.3.渲染使用 -->
        子组件{{ title }}
        <div>父组件是{{ abc }}</div>
        <button @click="changeFn">修改title</button>
    </div>
</template>
<script>
export default {
    // 1.2.通过props进行接收
    props: ['title', 'abc'],
    methods: {
        changeFn() {
            // 2.1.通过$emit,向父组件发送消息通知
            this.$emit('changeTitle', 'andy');
        },
    },
};
</script>
<style lang="less" scoped>
div {
    margin: 20px;
    border: 1px solid red;
}
</style>

在父组件中,通过在子组件的标签上监听相应事件,并在事件处理函数中获取子组件传递的数据

<template>
    <div class="App">
        App组件(父组件)
        <!-- 1.1给组件标签,添加属性的方式传值 -->
        <!-- 2.2父组件对消息进行监听 -->
        <SonBox v-bind:title="myTitle" :abc="father" @changeTitle="handleChange"></SonBox>
    </div>
</template>
<script>
import SonBox from './components/SonBox.vue';
export default {
    data() {
        return {
            myTitle: 'a son',
            father: 'App.vue',
        };
    },
    components: {
        SonBox,
    },
    methods: {
        // 2.3提供处理函数,提供逻辑
        handleChange(newTitle) {
            this.myTitle = newTitle;
        },
    },
};
</script>
<style lang="less"></style>

prop校验#

有时父组件传递给子组件的数据不是子组件要求的数据类型

比如一个进度条组件的进度数据是一个正整数,而为了避免父组件传递的是字符型数据而无法使用的情况,就需要使用 prop校验

为组件的 prop 指定 验证要求 ,不符合要求,控制台就会有 错误提示 ,帮助开发者快速发现错误

语法:

  1. 类型校验(常用)
  2. 非空校验
  3. 默认值
  4. 自定义校验

类型校验#

props:{
    校验的属性名:类型 // Number String Boolean Array Function ...
}

以进度条为例

准备进度条组件 BaseProgress.vue ,对于进度条所需数据 w 指定其类型为 String ,结果就会发现在控制台打印出了错误信息

其它校验#

完整写法,将要校验的属性名的值写成一个对象,在其中写校验规则

使用完整写法,可以进行更多方式的校验,甚至自定义校验规则

props: {
    校验的属性名: {
        type: 类型, // Number String Boolean ...
        required: true, // 是否必填
        default: 默认值,
        validator(value) {
            // 自定义校验逻辑
            return 是否通过校验;
        },
    },
},

完整代码

App.vue

<template>
    <div class="app">
        <BaseProgress :w="width"></BaseProgress>
    </div>
</template>

<script>
import BaseProgress from './components/BaseProgress.vue';
export default {
    data() {
        return {
            width: 30,
        };
    },
    components: {
        BaseProgress,
    },
};
</script>

<style></style>

BaseProgress.vue

如下对数据类型、非空、默认值都做了校验,并且自定义了数据范围的校验规则

<template>
    <div class="base-progress">
        <div class="inner" :style="{ width: w + '%' }">
            <span>{{ w }}%</span>
        </div>
    </div>
</template>

<script>
export default {
    // props: ['w'],
    // 1.基础写法(类型校验)
    // props: {
    //     w: String,
    // },
    // 2.完整写法(类型、是否必填、默认值、自定义校验)
    props: {
        w: {
            type: Number,
            required: true,
            default: 50,
            validator(value) {
                if (value >= 0 && value <= 100) {
                    return true;
                } else {
                    console.log('传入的prop w,必须是0-100的数字');
                    return false;
                }
            },
        },
    },
};
</script>

<style scoped>
.base-progress {
    height: 26px;
    width: 400px;
    border-radius: 15px;
    background-color: #272425;
    border: 3px solid #272425;
    box-sizing: border-box;
    margin-bottom: 30px;
}
.inner {
    position: relative;
    background: #379bff;
    border-radius: 15px;
    height: 25px;
    box-sizing: border-box;
    left: -3px;
    top: -2px;
}
.inner span {
    position: absolute;
    right: 0;
    top: 26px;
}
</style>

prop&data 单向数据流#

共同点:都可以给组件提供数据。

区别:

  • data 的数据是 自己的 ,随便改
  • prop 的数据是 外部的 ,不能直接改,要遵循 单向数据流 ,谁提供的数据谁修改

以计数器组件为例

若计数器的值为组件本身的数据,那在组件中点击就可以直接修改

然鹅实际开发中,类似计数器的数据一般由外部提供,那就不能直接count++/count--修改了

而是通过按钮绑定点击事件,用 this.$emit 通知父组件修改数据

BaseCount.vue

<template>
    <div class="base-count">
        <button @click="handleSub">-</button>
        <span>{{ count }}</span>
        <button @click="handleAdd">+</button>
    </div>
</template>

<script>
export default {
    // 1.自己的数据随便修改  (谁的数据 谁负责)
    // data () {
    //   return {
    //     count: 100,
    //   }
    // },
    // 2.prop外部传过来的数据 不能随便修改
    props: {
        count: Number,
    },
    methods: {
        // 子组件通知父组件修改数据,然后数据更新视图
        handleAdd() {
            // 子传父this.$emit(事件名,参数)
            this.$emit('changeCount', this.count + 1);
        },
        handleSub() {
            this.$emit('changeCount', this.count - 1);
        },
    },
};
</script>

<style>
.base-count {
    margin: 20px;
}
</style>

在父组件中子组件标签上绑定事件处理函数,接收参数对数据进行修改

App.vue

<template>
    <div class="app">
        <BaseCount :count="count" @changeCount="handleChange"></BaseCount>
    </div>
</template>

<script>
import BaseCount from './components/BaseCount.vue';
export default {
    components: {
        BaseCount,
    },
    data() {
        return {
            count: 100,
        };
    },
    methods: {
        handleChange(newCount) {
            this.count = newCount;
        },
    },
};
</script>

<style></style>

单向数据流 :父组件的prop更新,会单向向下流动,影响到子组件

组件通信案例-小黑记事本组件版#

需求说明:

  1. 拆分基础组件
  2. 渲染待办任务
  3. 添加任务
  4. 删除任务
  5. 底部合计和清空功能
  6. 持久化存储

拆分基础组件#

整个案例拆分为三个基础组件:TodoHeader.vue、TodoMain.vue、TodoFooter.vue

按步骤拆分结构,样式偷个懒不拆分直接main.js导入,之后父组件导入即可,略