1

写在开头

在编写组件时,记住是否要复用组件有好处。一次性组件跟其它组件紧密耦合没关系,但是可复用组件应当定义一个清晰的公开接口。--官方文档

在看这篇文章前,强烈推荐大家看这篇文章Thinking in Vue之一:组件扩展的尝试
项目Github
演示Demo

思考

就像官方文档所说的一样,自己通常写的都是一次性的组件,没有往可复用组件方面去思考。

前提

对vue的render函数有所了解,对自定义事件有所了解,没有也没关系。
render函数 文档
自定义事件 文档

可复用的Button组件的实现

实现一个基础的Button组件(顺便也将后面的css样式加入进来):

// src/components/BaseButton.vue
<script>
  export default{
    name: 'base-button',
    props: [],
    render(createElement) {
      return createElement(
        'a',
        {
          class: ['button', 'ripple'],
        },
        this.$slots.default
      )
    },
    methods: {
    }
  };
</script>
<style scoped>
/*button默认样式*/
  a.button {
    display: inline-block;
    line-height: 1;
    text-align: center;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    border: none;
    border-radius: 2px;
    position: relative;
    padding: 8px 30px;
    margin: 10px 1px;
    font-size: 14px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0;
    will-change: box-shadow, transform;
    transition: box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1), color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    text-decoration: none;
    background: transparent;
    background-color: #EEEEEE;
    color: rgba(0, 0, 0, 0.87);
  }

  a.button:active {
    box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
  }
    /*button 扩展样式*/
  a.button.default {
    background-color: #EEEEEE;
    color: rgba(0, 0, 0, 0.87);
  }

  a.button.primary {
    background-color: #009688;
    color: rgba(255, 255, 255, 0.84);
  }

  a.button.success {
    background-color: #4caf50;
    color: rgba(255, 255, 255, 0.84);
  }

  a.button.info {
    background-color: #03a9f4;
    color: rgba(255, 255, 255, 0.84);
  }

  a.button.warning {
    background-color: #ff5722;
    color: rgba(255, 255, 255, 0.84);
  }

  a.button.danger {
    background-color: #f44336;
    color: rgba(255,255,255, 0.84);
  }

  .ripple {
    position: relative;
    overflow: hidden
  }
/*button 水波纹点击效果*/
  .ripple:after {
    content: "";
    display: block;
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    pointer-events: none;
    background-image: radial-gradient(circle, #000 10%, transparent 10.01%);
    background-repeat: no-repeat;
    background-position: 50%;
    transform: scale(10, 10);
    opacity: 0;
    transition: transform .5s, opacity 1s
  }

  .ripple:active:after {
    transform: scale(0, 0);
    opacity: .2;
    transition: 0s
  }
</style>

完成了一个Button的基础样式,然后通过render函数来创建一个想过等同于<a class="button"></a>的template.
在App.vue中使用它:

    <BaseButton>Base</BaseButton>

渲染的结果:
BaseButton浏览器中显示

组件树的结果:
BaseButton渲染结果

此时这个BaseButton就是一个可用的组件了,那么如何复用它呢?这篇文章也有提到Thinking in Vue之一:组件扩展的尝试
根据这篇文章提供的思路,创建一个TypeButton组件然后将BaseButton组件给包裹起来,这样就不需要为每一个Button组件从新复制粘贴那些默认样式了。

实现一个基础的TypeButton组件

// src/components/TypeButton.vue
<script>
  import Button from './BaseButton.vue'
  export default{
    name: "type-button",
    render(createElement){
      return createElement(Button,
        {
            class:["info"]
        },
        this.$slots.default
      )
    },
    methods: {
    },
    components: {}
  };
</script>
<style scoped>
</style>

在App.vue中挂载上去
渲染的结果:
图片描述

DOM节点结果:
图片描述

组件树结果:
图片描述

当然,此时可以说完成了复用,但是并不完美。我们可以做一个工厂方法一样的组件复用。此时我们就会用到prop来传递属性。

// 进一步修改src/components/TypeButton.vue
<script>
  import Button from './BaseButton.vue'
  export default{
    name: "type-button",
    //将传递过来的type在props中注册,并进行验证
    props: {
      type: {
        validator: function (typeStr) {
          if (typeof typeStr === 'string') {
            switch (typeStr) {
              case 'defaul':
              case 'primary':
              case 'info':
              case 'success':
              case 'warning':
              case 'danger':
                return true;
              default:
                return false;
            }
          } else return false;
        }
      }
    },
    render(createElement){
      return createElement(Button,
        {
          class: [this.type],
          on:{
              click:this.click
          }
        },
        this.$slots.default
      )
    },
    methods: {
    },
    components: {}
  };
</script>
<style scoped>
</style>

此时我们就可以这样使用组件了

<TypeButton type="defaul">defaul</TypeButton>
<TypeButton type="primary">primary</TypeButton>
<TypeButton type="info">info</TypeButton>
<TypeButton type="success">success</TypeButton>
<TypeButton type="warning">warning</TypeButton>
<TypeButton type="danger">danger</TypeButton>

就很完美了有木有!!!!

实现自定义的点击事件(最后一部分)

此时如果你想这样来实现点击事件:<TypeButton @click="defualt" type="defaul">defaul</TypeButton>,你会发现是不可行的。这是就需要自定义事件。

// App.vue的methods
  methods: {
      defaul(){
        console.log('defaul')
      }
  }
// TypeButton.vue的render函数
<script>
  import Button from './BaseButton.vue'
  export default{
    render(createElement){
      return createElement(Button,
        {
          class: [this.type],
          //添加on对象
          on:{
              click:this.click
          }
        },
        this.$slots.default
      )
    },
    //添加
    methods: {
        click(){
            //触发组件中定义的click
            this.$emit('click')
        }
    },
  };
</script>
// BaseButton.vue
  export default{
    name: 'base-button',
    props: [],

    render(createElement) {
      return createElement(
        'a',
        {
          class: ['button', 'ripple'],
          on: {
            click: this.click
          }
        },
        this.$slots.default
      )
    },
    methods: {
      click(){
        this.$emit('click')
      }
    }
  };

此时在组件中使用@click<TypeButton @click="defualt" type="defaul">defaul</TypeButton>就可以被触发。

也就实现了一个Material Design风格,水波纹点击效果的Button组件。


placeless
197 声望3 粉丝