Vue组件化开发--组件通信方式-父传子、子传父、非父子组件传值

一、概述

以脚手架搭建的Vue项目为笔记背景。

如果将所有的代码逻辑全部放到一个组件中,代码是非常的臃肿和难以维护的。

并且在真实开发中,可能会有更多的内容和代码逻辑,对于扩展性和可维护性来说都是非常不友好的。

所以开发者,我们会把一个大功能进行拆分,拆分成一个个功能的小组件。

那组件多了,就必然涉及到组件之间的数据传递。

二、组件通信-父传子

2.1 props属性的作用和使用场景

父组件有一些数据,需要子组件来进行展示。这种情况可以通过props组合式API来完成组件之间的通信。

props可以在当前组件上注册一些自定义的attribute(属性)。

父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值。

2.2 props有两种常见的用法

  • 字符串数组,数组中的字符串就是attribute的名称

    props: ['name', 'age']
    
  • 对象类型,对象类型可以在指定attribute名称时,指定它的类型、是否是必须的、默认值等等

    props: {
        name: {
            type: String,				// 需要传递的数据类型
            required: true,				// 是否必须传递,不传递的话报错
            default: "张三"		  	   // 属性默认值 
        },								// 一般required和default只选其一配置即可
            			
        // 对象类型的默认值,必须是一个函数,使用函数返回值来确定默认值   
        friend: {
            type: Object,
            default() {
                 return { name: "james" }
            }
        },
        hobbies: {
            type: Array,
            default: () => ["篮球", "rap", "唱跳"]
        },
    }
    

如何选择?

数组类型它对传入的attribute的名称,并不能对其进行任何限制吗,而对象的写法则让props变得更加完善。

当使用对象语法的时候,我们可以对传入的内容作出很多限制:

  • 指定传入的attribute的类型;
  • 指定传入的attribute是否是必传的;
  • 指定没有传入时,attribute的默认值;

这些限制可以让程序更加严谨。通常都会选用这种方式去定义props的相关属性。

注意点:

  • 一般requireddefault只选其一配置即可
  • 对象类型的默认值,必须是一个函数,使用函数返回值来确定默认值

2.3 props属性的type类型支持

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

2.5 props属性命名问题

由于HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。

因此使用 DOM 模板时,驼峰命名法的 prop 名可以使用其等价的 短横线分隔命名方式来使用。

也可以直接使用驼峰命名法来使用。

定义时:

props: {
    showMessage: {
        type: String,
        default: "默认showMessage的值"
    }
}

使用时:

<show-info :age="100" showMessage="我的个人信息"/>

<show-info :age="100" show-message="我的个人信息"/>

更推荐使用时,用短横线分隔的方式来使用。

2.6 关于非Prop的Attribute

当我传递给一个组件某个属性,该属性并没有定义对应的props或者emits时,就称之为 非propsAttribute

常见的包括classstyleid等属性 。

这时有两种情况:

  • 当组件有单个根节点时,非propsAttribute将自动添加到根节点的标签属性中。

  • 如果组件有多个根节点,那么会报警告,必须手动的指定要绑定到哪一个属性上。

    • 手动指定的方式是使用$attrs来获取非propsAttribute

      <template>
      	<div class="infos">
              <h2 :class="$attrs.class">姓名: {{ name }}</h2>
              <h2>年龄: {{ age }}</h2>
          </div>
      </template>
      

上面的情况就叫做属性继承。

如果不希望组件的根元素继承attribute,可以在组件中设置 :

export default {
	inheritAttrs: false
}

上面设置可以禁用Attribute继承和多根节点的方式。

2.7 代码示例

1)父组件
<template>
    <!-- 展示个人信息的组件 -->
    <!-- 如果当前的属性是一个非prop的attribute, 那么该属性会默认添加到子组件的根元素上 -->
    <show-info name="jack" 
               :age="25" 
               :height="1.78" 
               address="广州市" 
               abc="cba" 
               class="active" />

</template>

<script>
    // 导入子组件
    import ShowInfo from './ShowInfo.vue'

    export default {
        // 局部注册子组件
        components: {
            ShowInfo
        }
    }
</script>

<style scoped>
</style>

2)子组件

<template>
    <div class="infos">
        <!-- 获取继承而来的class属性 -->
        <h2 :class="$attrs.class">姓名: {{ name }}</h2>
        <h2>年龄: {{ age }}</h2>
        <h2>身高: {{ height }}</h2>
        <h2>Message: {{ showMessage }}</h2>
    </div>
	<!-- v-bind="$attrs 一次性把所有继承而来的属性全部绑定到当前元素上面 -->
    <div class="others" v-bind="$attrs"></div>
</template>

<script>
    export default {
        //inheritAttrs: false,		// 设置不允许属性继承
        props: {					// props对象语法定义属性
            name: {
                type: String,
                default: "我是默认name"
            },
            age: {
                type: Number,
                required: true
            },
            height: {
                type: Number,
                default: 1.75
            },
            // 对象类型写默认值时, 需要编写default的函数, 函数返回默认值
            friend: {
                type: Object,
                default() {
                    return { name: "james" }
                }
            },
            hobbies: {
                type: Array,
                default: () => ["篮球", "rap", "唱跳"]
            },
            showMessage: {
                type: String,
                default: "我是showMessage"
            }
        }
    }
</script>

<style scoped>
</style>


三、组件通信-子传父

3.1 使用场景

  • 当子组件有一些事件被触发时,比如在组件中发生了点击,父组件需要作出一定的响应;

  • 子组件有一些内容想要传递给父组件的时候;

3.2 操作步骤

  • 在子组件中定义好在某些情况下触发的事件名称(自定义事件);

  • 在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中;

  • 在子组件中发生某个事件的时候,根据事件名称触发对应的事件;

3.3 自定义事件实现子向父传递

子组件:

<template>
  <div class="add">
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
  </div>
</template>

<script>
  export default {
    methods: {
      btnClick(count) {
        // 当btnClick方法被调用时,子组件会发出去一个自定义事件
        // 第一个参数自定义的事件名称
        // 第二个参数是传递的参数
        this.$emit("add", count)
      }
    }
  }
</script>

父组件:

<template>
  <div class="app">
    <h2>当前计数: {{ counter }}</h2>

    <!-- 并且监听组件add-counter内部的add事件 -->
    <add-counter @add="addBtnClick"></add-counter>
  </div>
</template>

<script>
  //导入组件
  import AddCounter from './AddCounter.vue'

  export default {
    // 注册组件
    components: {
      AddCounter,
    },
    data() {
      return {
        counter: 0
      }
    },
    methods: {
      // 子组件中add事件触发时,将调用此方法
      addBtnClick(count) {
        this.counter += count
      },
    }
  }
</script>

简单来说:

  • 父组件内, 在子组件标签上添加 @自定义事件="父methods函数"

  • 子组件内, 恰当时机this.$emit('自定义事件名', 值)

4.3 关于Vue3.x对上面方式传值的优化

假设开发父组件和子组件的是两个人,那么开发父组件的人在使用子组件时。

一时是找不到子组件中发出的自定义事件在哪里的。

因为这都是写在业务逻辑里,代码量比较多的时候确实比较难找。而且这些事件也缺乏有效的管理。

因此Vue3.x提供了emits组合式API的写法去优化上面的内容。

1)数组写法对发出的事件进行注册说明
<template>
  <div class="add">
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
  </div>
</template>

<script>
  export default {
    // emits数组语法(把发出的自定义事件全部写到这里面)
    // 相当于把发出的事件进行注册说明一下,这个写法是非必须的
    emits: ["add"],
    methods: {
      btnClick(count) {
        this.$emit("add", count)
      }
    }
  }
</script>
  • emits数组写法有两个好处
    • 让调用者更清晰子组件中发出了什么事件
    • 使用vscode开发的话,还可以获得事件提示,对开发更加友好,如果不写则没有事件提示
2)对象写法对发出的事件参数进行参数验证
<template>
  <div class="add">
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
  </div>
</template>

<script>
  export default {
    // emmits对象语法,对参数count进行验证
    emits: {
      add: function(count) {
        if (count <= 10) {
           return true
        }
        return false
      }
    },
    methods: {
      btnClick(count) {
        console.log("btnClick:", count)
        this.$emit("add", 100)
      }
    }
  }
</script>

对象语法在写业务逻辑的时候用的可能会少一些,一般只用数组语法来进行优化就可以了。

除非是封装一些严格的组件库的时候,可以对事件参数作出相应的验证。

四、组件通信-非父子组件

4.1 Vue3中的Provide/Inject(依赖注入)方式

1)概念和使用场景

Provide/Inject用于非父子组件之间共享数据。这也是Vue3新提供的一些特性。

如果有些深度嵌套的组件,子组件想要获取父组件的部分内容。

在这种情况下,如果仍然将props沿着组件链逐级传递下去,就会非常的麻烦。

此时可以使用 ProvideInject ,无论层级结构有多深,父组件都可以作为其所有子组件的依赖 提供者。

父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

父组件不需要知道哪些子组件使用它使用了它提供的属性。 子组件也不需要知道 注入的属性来自哪里。

2)代码示例

A组件:

<template>
  <div class="app"></div>
</template>

<script>
  import Home from './B.vue'
  export default {
    components: {
      B
    },
    // provide一般都是写成函数
    provide() {
      return {
        name: "张三",
        age: 25
      }
    }
  }
</script>

父组件提供一个对象数据。

B组件:

<template>
  <div class="app"></div>
</template>

<script>
  import Home from './C.vue'
  export default {
    components: {
      C
    },
  }
</script>

C组件:

<template>
  <div class="banner">
    <h2> {{ name }} - {{ age }}</h2>
  </div>
</template>

<script>
  export default {
    inject: ["name", "age"]
  }
</script>

C组件并不是A组件的直接子组件。但是可以使用inject的方式接收到。

今后开发Vue3项目,可能这种方式用的不多,因为可以使用Vuex或者pinia去共享数据。

4.2 Vue3中的事件总线方式

Vue3从实例中移除了 $on$off$once 方法。

也就是说我们无法像Vue2中那种方式去使用事件总线。也不能直接把公共Vue

所以我们如果希望继续使用全局事件总线,可以通过 Vue3官方有推荐一些库。

mitt或者tiny-emitter。 这里就以mitt为例。

1)npm安装
num install mitt -s
2)新建一个event-bus.js作为事件总线
import mitt from 'mitt'
const emitter = mitt()
export default emitter
3)A组件触发自定义事件
<template>
    <div>
        <h1>A组件</h1>
        <button @click>
            按钮
    	</button>
    </div>
</template>
<script>
  import emitter from '../plugins/event-bus.js'
  export default {      
      created(){
          emitter.emit("fn",{name:'张三',age:19})
      }
  }
</script>
4)B组件监听和移除自定义事件
<template>
    <div>
    	<h1>B组件</h1>
        {{ str }}
    </div>
    </template>
<script >
  import emitter from '../plugins/event-bus.js'
  export default {      
      created(){
          // 监听fn事件
          emitter.on("fn",msg=>{
              console.log(msg);
          });
      },
      methods: {
          eventHandler() {
              console.log("已经终止事件监听")
          }
      },
      unmounted() {
          // 取消监听fn函数
          eventBus.off("fn", this.eventHandler)
      }
  }
</script>

按照更加严谨的做法,组件中需要监听的同时也要取消监听。

这种方式不要乱用,如果在代码中大量的使用事件总线代码,其实是不便于管理和维护的

你可能感兴趣的:(Vue全家桶,vue.js,javascript,前端)