Vue组件封装

352 阅读4分钟

要点概览

  • props参数
  • event自定义事件
  • slot定制插槽
  • 组件通信

组件封装及使用

1.建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。   

2.准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。

3.准备好组件的数据输出。即根据组件逻辑,做好要暴露出来的方法。

4.封装完毕了,引用(全局、局部),注册,使用组件

import FormDialog from '@/components/dialog/formDialog'

components: {
    FormDialog
},

<FormDialog> </FormDialog>

好处

  • 提高代码复用性和开发效率

  • 减少代码冗余

1、props,组件属性

很多时候,需要外界传递数据给组件,使其根据不同数据,不同的场景,渲染不同样式。

通过props,数据可从 父组件传入子组件。

1.1 给组件添加属性

命名格式,camelCase

一般写法,只提供属性名

props:['属性1', '属性2', '属性3', ...]

如:

<script> 
export default { 

  props:['lon','lat'], 

} 

</script>  

具体写法,指定类型,是否必传,默认值 等

 props: {
  // 单类型
  propA: Number,
  // 多种类型
  propB: [String, Number],
  // 必传
  propC: {
    type: String,
    required: true, 
  },
  // 有默认值
  propD: {
    type: Number,
    default: 100,
  },
  // 数组/对象类型,默认值 由一个工厂函数返回
  propE: {
    type: Object,
    default: function () {
      return { message: 'hello' };
    },
  },
  // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10;
    },
  },
},

添加完属性,就可以又父组件给它赋 属性值

<ChildView propA="标题" :propB="fatherData"/>

单向绑定原则

所有的 props ,都因父组件的更新而变化,自然地将新的状态向子组件传递,而不能逆向传递。

在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。

props传过来的数据只做展示,不得修改,想修改,再新写一个data中的变量承接做数据的再处理。

深浅拷贝

当props的类型是对象或数组时,虽然子组件更改 props 值,仍然可以更改父组件的数据。这是因为 JavaScript 的对象和数组是按引用传递。

inheritAttrs属性

组件的根元素是否继承特性

2、事件传递

子组件触发一个事件,往往需要 传递给父组件,交给父组件来处理相关逻辑

$emit

实现:子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件,$emit第二个参数为传递的数值,可用于传参

// 子组件中的按钮
<button @click="$emit('onClick')">点击</button>


// 父组件调用子组件
<child @onClick="handleClick" />

传参

通过事件的传递,实现了 子组件可将某些参数向父组件传递。解决了props的局限

// 子组件方法
clickBtn(){
    // 触发父组件方法,并传递参数data到父组件
    this.$emit('onClick', 1)
    this.$emit('onClick', {tab: 'xxxx'})

}


// 父组件调用子组件
<child @onClick="handleClick" />

// ... ...

// 父组件中被触发的方法,接受到子组件传来的参数
handleClick(val){
    // val 为传过来的值
}

可以添加校验

有些场景,在子组件中,也可以把校验相关的代码封装进去

父组件中的逻辑要放在父组件处理,子组件基于父组件的数据做的逻辑放在子组件中处理; 这样既降低了耦合性,也保证了各自的数据不被污染。

// 子组件中
export default {
  emits: {
    // 没有校验
    click: null,

    // 校验 submit 事件
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        // 无效
        return false
      }
    }
  },
  
  methods: {
    clickBtn(email, password) {
      this.$emit('submit', { email, password })
    }
  }
}

3、slot 插槽

3.1 背景

使用props父向子传递数据,传递的数据都是对象、数组、字符串等"js类型的数据"。有时候我们想要传递大量的html类型的片段。

一个通用组件,往往不能够完美的适应所有应用场景 所以在封装组件的时候,只需要完成组件 80% 的功能,剩下的 20% 让父组件通过 solt 解决。

只需要在合适的位置留一个 slot,将按钮的位置留出来,然后在父组件写入按钮。

组件化编程中,css不怎么需要传递,因为我们可以通过深度作用域选择器,如/deep/去在父组件中选中子组件中的dom元素去设置样式

3.2 分类

  • 默认插槽

又叫:普通插槽、单个插槽,匿名插槽,即不带名字的,不用设置name属性 这样的

<template>
  <div class="box">
    <h1>我是子组件</h1>
    <slot></slot>
<!-- 子组件写了一个这样的slot标签,才能接收到父组件传递过来的html片段。不写的话父组件传了也是白传即不会生成DOM,也不会渲染
  -->
  </div>
</template>
<template>
  <div id="app"> 
    <child>
      <div slot="default">我是插槽内容</div>    //slot="default"可以省略
    </child>
  </div>
</template>
  • 具名插槽

带个名字的 <slot name="footer"></slot>这样的,拥有name属性

应用场景,放置在特定位置的插槽

<template>
  <div id="app"> 
    <child>
      <div slot="header">我是插槽内容</div>    //slot="header"不可以省略
    </child>
  </div>
</template>

写法二,模板标签不会渲染成DOM

<template>
  <div id="app"> 
    <child>
      <template slot="header">    //  简写成<template #header>
        <p>模板标签头部</p>
      </template>
    </child>
  </div>
</template>
  • 作用域插槽

插槽的略微高级点用法,比较少用到

作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过slot-scope来接受数据。

slot 中可以作为一个作用域,在子组件中定义变量,然后在父组件中自定义渲染的方式

<template>
  <div class="">
    <slot :user="user"> </slot>    //:user="user",就是插槽所携带的数据
  </div>
</template>
<script>
export default {
  data() {
    return {
      user: {
        name: "周星驰",
        age: 50,
      },
    };
  },
};
</script>

在父组件中通过slot-scope来接受子组件提供的参数

<template>
  <div class="father">
    <child>
      <template slot-scope="{ user }">  
        <div>{{user.name}}</div>
        <div>{{user.age}}</div>
      </template>
    </child>
  </div>
</template> 

插槽内容无法访问子组件的数据。

4、实现 双向绑定 v-model

在组件上 使用,以实现属性的双向绑定

<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

使用

<CustomInput v-model="editText" />

另一个实现方式:

<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
}
</script>

<template>
  <input v-model="value" />
</template>

4、组件之间的其他通信方式

4.1 parent/parent / children

子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。

// 父组件
data() {
    return {
        msg:'age'
    }
},
components: {
    Hear,//子组件
},
methods: {
    test() {
        alert(1)
    }

},

// 子组件里
methods: {

    click() {
      console.log(this.$parent.msg);//访问 父组件的数据
      this.$parent.test()  //调用 父组件的方法
    }
  }

在父组件中使用 this.$children 他得到的是一个数组[VueComponent, VueComponent],数组中的值就是每一个组件实例。

节制地使用 parentparent 和 children , 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信

4.2 ref

通过this.$refs, 可以访问到此vue实例中的所有设置了ref属性的DOM元素,并对其进行操作。

ref='xx'相当于id='xx'

this.$refs.xx去找具体的位置

<Children ref="foo" />  
  
this.$refs.foo  // 获取子组件实例,通过子组件实例就能拿到对应的数据  

4.3 attrs/attrs / listeners

利用 $attrs 实现祖孙组件间的数据传递, $listeners 实现祖孙组件间的事件监听

$attrs

会继承所有的父组件属性(除了 prop 传递的属性、class 和 style ),一般用在子组件的子元素上。可以通过 v-bind="$attrs" 传入内部组件。在创建高级别的组件时非常有用。

$ listeners

它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)

inheritAttrs: 为true 继承除 props 之外的所有属性;为false 只继承 class style 属性

其他

Bus

使用场景:兄弟组件传值。

1.创建一个中央时间总线EventBus

2.兄弟组件通过emit触发自定义事件,emit触发自定义事件,emit第二个参数为传递的数值

3.另一个兄弟组件通过$on监听自定义事件

image.png

provide / inject

在祖先组件定义provide属性,返回传递的值。在后代组件通过inject接收组件传递过来的值

provide(){
  return(){
    foo: 'xxxxxx'
  }
inject:['foo'] // 获取到祖先组件传递过来的值