vue中使用$attrs高效封装组件

524 阅读1分钟

$attrs属于vue的实例属性,说到实例属性想必用到最多的当属$refs了,而其他的属性我们可能比较陌生。 谈到$attrs我们要和$props区别开来。

1.$props$attrs是什么

首先通过官方文档了解二者使用:

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

文档说的还是蛮清晰的,下面通过具体的小例子感受一下

2.例子

子组件定义:

<template>
  <div>这是车辆信息卡片</div>
</template>
<script>
export default {
  props: {
    test: String
  },
  data() {
    return {}
  },
  created() {
    console.log('props', this.$props)
    console.log('attrs', this.$attrs)
  }
}
</script>

如上代码中,子组件定义了props。

父组件定义:

<div>
  <PreviewCard>
    <StoreInfoCard :storeInfo="storeInfo"  :test="'123'"/>
  </PreviewCard>
</div>

如上代码中父组件额外传递了storeInfo属性,在子组件中通过$attrs获取, 如下图所示

如果子组件的定义如下:

<template>
  <div>这是车辆信息卡片</div>
</template>
<script>
export default {
  data() {
    return {}
  },
  created() {
    console.log('props', this.$props)
    console.log('attrs', this.$attrs)
  }
}
</script>

没有定义props,那么所有传递的数据都被识别为attrs

我们发现,只有子组件定义了props,$props才能获取到。如果子组件没有有定义可以通过$attrs获取。

3.使用$attrs进行组件封装

在对UI组件进行二次封装时,使用$attrs更加方便,并且这样可以减少子组件props中定义很多属性的工作量。我们通过对el-dialog的封装来体会一下。

3.1通过定义props的方式:

<template>
    <el-dialog :title="title"
               :visible.sync="innerVisible"
               :close-on-click-modal="closeOnClickModal"
               :center="center"
               :append-to-body="appendToBody"
               :class="dialogClass"
               :lock-scroll="true"
               @open="open"
               @opened="opened"
               @close="close"
               @closed="closed"
               :ref="refName"
               :before-close="handleClose">
        <el-scrollbar ref="dialogScrollbar">
            <div class="dialog-content">
                <slot/>
            </div>
        </el-scrollbar>
        <template slot="footer">
            <slot slot="footer"
                  name="footer"></slot>
        </template>
        <slot slot="footer"
              name="footer"></slot>
    </el-dialog>
</template>

<script>
export default {
  name: 'MyDialog',
  props: {
    // Dialog 的标题
    title: String,
    // 是否显示 Dialog,支持 .sync=修饰符
    visible: {
      type: Boolean,
      default: false
    },
    // 是否可以通过点击 modal 关闭 Dialog
    closeOnClickModal: {
      type: Boolean,
      default: true
    },
    // 自身是否插入至 body 元素上
    appendToBody: {
      type: Boolean,
      default: false
    },
    // 是否对头部和底部采用居中布局
    center: {
      type: Boolean,
      default: true
    },
    refName: {
      type: String,
      default: 'mxDialog'
    },
    handleClose:Function
  },
}
</script>

3.2 通过$attrs定义的dialog

<template>
  <el-dialog v-drag class="dialog" v-bind="attrs" v-on="$listeners">
    <template v-for="item in slots" v-slot:[item]>
      <slot :name="item">
        <div v-if="item === 'title'" :key="item" class="dialog-title">
          {{ attrs.title }}
        </div>
        <span v-if="item === 'footer' && showFooter" :key="item">
          <el-button size="small" @click="handleCancel">取 消</el-button>
          <el-button
            size="small"
            :loading="loading"
            type="primary"
            @click="handleConfirm"
            >确 定</el-button
          >
        </span>
      </slot>
    </template>
  </el-dialog>
</template>

<script>
export default {
  name: 'v-dialog',
  inheritAttrs: false,
  props: {
    showFooter: {
      type: Boolean,
      default: true
    },
    loading: {
      type: Boolean,
      default: false
    }
  },
}
</script>

通过对比组件模板部分我们可以发现使用$attrs的方式定义组件更加简洁,不用定义那么多props, 这样的方式封装组件更加高效。

在第二个版本中我们在模板(template)部分还可以看到$listeners,文档中也有对其的定义:

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

总结

本文首先介绍了$props和$attrs 两个常用API的含义,然后通过小例子了解了它们的使用方法,最后通过对比使用$attrs和props定义的组件体会了使用$attrs的简洁高效。